From 18da914bf9708f73386cb47808777fcf8aea0568 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:42:19 +0100 Subject: [PATCH] =?UTF-8?q?fix=20theme=20issues,=20remove=20dead=20rainbow?= =?UTF-8?q?=20mode=20code,=20standardize=20theme=20us=E2=80=A6=20(#6668)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix issues with the theme of the app that caused some things to persist in light mode/dark mode whilst the rest of the app was the opposite theme. Removed dead rainbow mode code. Added system theme option to settings. --- .../public/locales/en-US/translation.toml | 3 +- frontend/editor/src/core/App.tsx | 4 +- .../src/core/components/AppProviders.tsx | 6 +- .../src/core/components/layout/Workbench.tsx | 11 +- .../core/components/shared/AppConfigModal.tsx | 19 +- .../src/core/components/shared/Footer.tsx | 12 +- .../core/components/shared/QuickAccessBar.tsx | 4 +- .../shared/RainbowThemeProvider.tsx | 79 ---- .../core/components/shared/ThemeProvider.tsx | 101 +++++ .../core/components/shared/TopControls.tsx | 4 - .../config/configSections/GeneralSection.tsx | 17 +- .../shared/quickAccessBar/QuickAccessBar.css | 17 +- .../core/components/tools/RightSidebar.tsx | 7 +- frontend/editor/src/core/constants/theme.ts | 37 +- .../editor/src/core/hooks/useCookieConsent.ts | 101 ++--- .../editor/src/core/hooks/useRainbowTheme.ts | 203 ----------- .../src/core/services/preferencesService.ts | 4 +- .../editor/src/core/styles/rainbow.module.css | 345 ------------------ frontend/editor/src/core/styles/zIndex.ts | 1 - .../SetupWizard/DesktopAuthLayout.tsx | 21 -- .../components/SetupWizard/desktopOAuth.css | 4 +- frontend/editor/src/proprietary/App.tsx | 4 +- .../proprietary/components/chat/ChatFAB.tsx | 29 +- .../proprietary/components/policies/README.md | 2 +- .../editor/src/proprietary/routes/Landing.tsx | 9 +- .../editor/src/proprietary/routes/Login.tsx | 9 +- .../routes/authShared/AuthLayout.tsx | 23 +- .../proprietary/routes/authShared/auth.css | 7 + .../src/proprietary/styles/auth-theme.css | 2 +- frontend/editor/src/prototypes/App.tsx | 4 +- frontend/editor/src/saas/App.tsx | 4 +- .../shared/charts/StackedBarChart.css | 14 + .../shared/charts/StackedBarChart.tsx | 44 +-- .../shared/charts/utils/themeUtils.ts | 68 ---- .../src/saas/routes/authShared/AuthLayout.tsx | 23 +- .../src/saas/routes/authShared/saas-auth.css | 32 +- .../editor/src/saas/styles/saas-theme.css | 26 +- frontend/editor/src/saas/types/charts.ts | 1 - 38 files changed, 319 insertions(+), 982 deletions(-) delete mode 100644 frontend/editor/src/core/components/shared/RainbowThemeProvider.tsx create mode 100644 frontend/editor/src/core/components/shared/ThemeProvider.tsx delete mode 100644 frontend/editor/src/core/hooks/useRainbowTheme.ts delete mode 100644 frontend/editor/src/core/styles/rainbow.module.css create mode 100644 frontend/editor/src/saas/components/shared/charts/StackedBarChart.css delete mode 100644 frontend/editor/src/saas/components/shared/charts/utils/themeUtils.ts diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 8e969fca87..5133ff6ce6 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -6735,8 +6735,9 @@ logout = "Log out" setAsDefault = "Set as Default" theme = "Theme" themeDark = "Dark" -themeDescription = "Switch between light and dark mode" +themeDescription = "Choose light, dark, or follow your system" themeLight = "Light" +themeSystem = "System" title = "General" user = "User" diff --git a/frontend/editor/src/core/App.tsx b/frontend/editor/src/core/App.tsx index 66b97beb72..566e861520 100644 --- a/frontend/editor/src/core/App.tsx +++ b/frontend/editor/src/core/App.tsx @@ -3,7 +3,7 @@ import { Routes, Route } from "react-router-dom"; import { AppProviders } from "@app/components/AppProviders"; import { AppLayout } from "@app/components/AppLayout"; import { LoadingFallback } from "@app/components/shared/LoadingFallback"; -import { RainbowThemeProvider } from "@app/components/shared/RainbowThemeProvider"; +import { ThemeProvider } from "@app/components/shared/ThemeProvider"; import { PreferencesProvider } from "@app/contexts/PreferencesContext"; import HomePage from "@app/pages/HomePage"; import MobileScannerPage from "@app/pages/MobileScannerPage"; @@ -22,7 +22,7 @@ import "@app/utils/fileIdSafety"; function PublicRouteProviders({ children }: { children: React.ReactNode }) { return ( - {children} + {children} ); } diff --git a/frontend/editor/src/core/components/AppProviders.tsx b/frontend/editor/src/core/components/AppProviders.tsx index d9f4864d8d..f8855e2ae9 100644 --- a/frontend/editor/src/core/components/AppProviders.tsx +++ b/frontend/editor/src/core/components/AppProviders.tsx @@ -1,5 +1,5 @@ import { ReactNode, useEffect } from "react"; -import { RainbowThemeProvider } from "@app/components/shared/RainbowThemeProvider"; +import { ThemeProvider } from "@app/components/shared/ThemeProvider"; import { FileContextProvider } from "@app/contexts/FileContext"; import { NavigationProvider } from "@app/contexts/NavigationContext"; import { ToolRegistryProvider } from "@app/contexts/ToolRegistryProvider"; @@ -114,7 +114,7 @@ export function AppProviders({ }: AppProvidersProps) { return ( - + - + ); } diff --git a/frontend/editor/src/core/components/layout/Workbench.tsx b/frontend/editor/src/core/components/layout/Workbench.tsx index 086e7de3da..4817616259 100644 --- a/frontend/editor/src/core/components/layout/Workbench.tsx +++ b/frontend/editor/src/core/components/layout/Workbench.tsx @@ -1,6 +1,5 @@ import { useEffect, useState, Suspense, lazy } from "react"; import { Box, Loader, Center } from "@mantine/core"; -import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider"; import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; import { useFileHandler } from "@app/hooks/useFileHandler"; import { useFileState } from "@app/contexts/FileContext"; @@ -34,7 +33,6 @@ const FileManagerView = lazy( // No props needed - component uses contexts directly export default function Workbench() { - const { isRainbowMode } = useRainbowThemeContext(); const { config } = useAppConfig(); // The consent banner used to be initialised by the footer; the legal links @@ -199,14 +197,7 @@ export default function Workbench() { {/* Workbench Bar - animates in/out based on file presence */} {currentView !== "myFiles" && diff --git a/frontend/editor/src/core/components/shared/AppConfigModal.tsx b/frontend/editor/src/core/components/shared/AppConfigModal.tsx index f5517c0a29..365d42608a 100644 --- a/frontend/editor/src/core/components/shared/AppConfigModal.tsx +++ b/frontend/editor/src/core/components/shared/AppConfigModal.tsx @@ -153,17 +153,18 @@ const AppConfigModalInner: React.FC = ({ const canProceed = await confirmIfDirty(); if (!canProceed) return; - // Pop back to whatever the user came from (files / viewer / tools). - // location.key === "default" means /settings was the first entry in - // this tab (deep link / refresh), so there's nothing to pop to; - // fall back to home in that case. - if (location.key === "default") { - navigate("/", { replace: true }); - } else { - navigate(-1); + // Only unwind history if settings was opened via the URL; opened via state + // there's no /settings entry to pop and navigate(-1) would jump to /files. + if (location.pathname.startsWith("/settings")) { + // "default" key = first entry (deep link/refresh); nothing to pop to. + if (location.key === "default") { + navigate("/", { replace: true }); + } else { + navigate(-1); + } } onClose(); - }, [confirmIfDirty, location.key, navigate, onClose]); + }, [confirmIfDirty, location.key, location.pathname, navigate, onClose]); // Synchronous wrapper for contexts (e.g. tour buttons) that need () => void const handleCloseSync = useCallback(() => { diff --git a/frontend/editor/src/core/components/shared/Footer.tsx b/frontend/editor/src/core/components/shared/Footer.tsx index 5000ee0be0..e73a48fd2a 100644 --- a/frontend/editor/src/core/components/shared/Footer.tsx +++ b/frontend/editor/src/core/components/shared/Footer.tsx @@ -10,7 +10,6 @@ interface FooterProps { cookiePolicy?: string; impressum?: string; analyticsEnabled?: boolean; - forceLightMode?: boolean; } export default function Footer({ @@ -20,7 +19,6 @@ export default function Footer({ cookiePolicy, impressum, analyticsEnabled, - forceLightMode = false, }: FooterProps) { const { t } = useTranslation(); const { footerInfo } = useFooterInfo(); @@ -38,7 +36,6 @@ export default function Footer({ const { showCookiePreferences } = useCookieConsent({ analyticsEnabled: finalAnalyticsEnabled, - forceLightMode, }); // Default URLs @@ -56,12 +53,8 @@ export default function Footer({
((_, ref) => { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); - const { isRainbowMode } = useRainbowThemeContext(); const { isFilesModalOpen } = useFilesModalContext(); const { handleReaderToggle, @@ -809,7 +807,7 @@ const QuickAccessBar = forwardRef((_, ref) => { ref={ref} data-sidebar="quick-access" data-tour="quick-access-bar" - className={`h-screen flex flex-col w-16 quick-access-bar-main ${isRainbowMode ? "rainbow-mode" : ""}`} + className="h-screen flex flex-col w-16 quick-access-bar-main" > {/* Fixed header outside scrollable area */}
diff --git a/frontend/editor/src/core/components/shared/RainbowThemeProvider.tsx b/frontend/editor/src/core/components/shared/RainbowThemeProvider.tsx deleted file mode 100644 index 0e6ba64a2d..0000000000 --- a/frontend/editor/src/core/components/shared/RainbowThemeProvider.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { createContext, useContext, useEffect, ReactNode } from "react"; -import { MantineProvider } from "@mantine/core"; -import { useRainbowTheme } from "@app/hooks/useRainbowTheme"; -import { mantineTheme } from "@app/theme/mantineTheme"; -import rainbowStyles from "@app/styles/rainbow.module.css"; -import { ToastProvider } from "@app/components/toast"; -import ToastRenderer from "@app/components/toast/ToastRenderer"; -import { ToastPortalBinder } from "@app/components/toast"; -import type { ThemeMode } from "@app/constants/theme"; -// SUI shared design-system tokens (used by @shared/components). Additive — its -// var names don't clash with the editor's own theme.css. The effect below -// bridges Mantine's color scheme to the `data-theme` attribute SUI keys on. -import "@shared/tokens/tokens.css"; - -interface RainbowThemeContextType { - themeMode: ThemeMode; - isRainbowMode: boolean; - isToggleDisabled: boolean; - toggleTheme: () => void; - activateRainbow: () => void; - deactivateRainbow: () => void; -} - -const RainbowThemeContext = createContext(null); - -export function useRainbowThemeContext() { - const context = useContext(RainbowThemeContext); - if (!context) { - throw new Error( - "useRainbowThemeContext must be used within RainbowThemeProvider", - ); - } - return context; -} - -interface RainbowThemeProviderProps { - children: ReactNode; -} - -export function RainbowThemeProvider({ children }: RainbowThemeProviderProps) { - const rainbowTheme = useRainbowTheme(); - - // Determine the Mantine color scheme - const mantineColorScheme = - rainbowTheme.themeMode === "rainbow" ? "dark" : rainbowTheme.themeMode; - - // Bridge the resolved scheme to the `data-theme` attribute SUI's tokens.css - // keys its dark palette on (the editor otherwise only sets Mantine's - // data-mantine-color-scheme), so @shared/components theme correctly. - useEffect(() => { - document.documentElement.setAttribute( - "data-theme", - mantineColorScheme === "dark" ? "dark" : "light", - ); - }, [mantineColorScheme]); - - return ( - - -
- - - {children} - - -
-
-
- ); -} diff --git a/frontend/editor/src/core/components/shared/ThemeProvider.tsx b/frontend/editor/src/core/components/shared/ThemeProvider.tsx new file mode 100644 index 0000000000..2dda9b8d39 --- /dev/null +++ b/frontend/editor/src/core/components/shared/ThemeProvider.tsx @@ -0,0 +1,101 @@ +import { + createContext, + useCallback, + useContext, + useMemo, + useState, + ReactNode, +} from "react"; +import { MantineProvider } from "@mantine/core"; +import { useIsomorphicEffect } from "@mantine/hooks"; +import { usePreferences } from "@app/contexts/PreferencesContext"; +import { mantineTheme } from "@app/theme/mantineTheme"; +import { ToastProvider } from "@app/components/toast"; +import ToastRenderer from "@app/components/toast/ToastRenderer"; +import { ToastPortalBinder } from "@app/components/toast"; +import { + type ThemeMode, + getSystemTheme, + resolveColorScheme, +} from "@app/constants/theme"; +// SUI shared design-system tokens (used by @shared/components); key on `data-theme`. +import "@shared/tokens/tokens.css"; + +interface ThemeContextType { + themeMode: ThemeMode; + setTheme: (mode: ThemeMode) => void; +} + +const ThemeContext = createContext(null); + +export function useTheme() { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme must be used within ThemeProvider"); + } + return context; +} + +interface ThemeProviderProps { + children: ReactNode; +} + +export function ThemeProvider({ children }: ThemeProviderProps) { + // preferences.theme is the single source of truth. + const { preferences, updatePreference } = usePreferences(); + const themeMode = preferences.theme; + const setTheme = useCallback( + (mode: ThemeMode) => updatePreference("theme", mode), + [updatePreference], + ); + + // Track the OS scheme so "system" updates live; only subscribe while on system. + // If matchMedia is unavailable we bail out and keep the seeded value, which + // getSystemTheme already defaults to light — so it never crashes. + const [systemScheme, setSystemScheme] = useState(getSystemTheme); + useIsomorphicEffect(() => { + if (themeMode !== "system") return; + if ( + typeof window === "undefined" || + typeof window.matchMedia !== "function" + ) { + return; + } + const media = window.matchMedia("(prefers-color-scheme: dark)"); + const update = () => setSystemScheme(media.matches ? "dark" : "light"); + update(); + media.addEventListener("change", update); + return () => media.removeEventListener("change", update); + }, [themeMode]); + + // The theme preference resolved to a concrete light/dark scheme. + const colorScheme = resolveColorScheme(themeMode, systemScheme); + + // Mantine drives `data-mantine-color-scheme`; mirror it to `data-theme` for SUI tokens. + useIsomorphicEffect(() => { + document.documentElement.setAttribute("data-theme", colorScheme); + }, [colorScheme]); + + const value = useMemo( + () => ({ themeMode, setTheme }), + [themeMode, setTheme], + ); + + return ( + + +
+ + + {children} + + +
+
+
+ ); +} diff --git a/frontend/editor/src/core/components/shared/TopControls.tsx b/frontend/editor/src/core/components/shared/TopControls.tsx index 573ee65afb..8fc61183a8 100644 --- a/frontend/editor/src/core/components/shared/TopControls.tsx +++ b/frontend/editor/src/core/components/shared/TopControls.tsx @@ -1,7 +1,5 @@ import React, { useState, useCallback, useMemo } from "react"; import { SegmentedControl, Loader } from "@mantine/core"; -import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider"; -import rainbowStyles from "@app/styles/rainbow.module.css"; import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; import GridViewIcon from "@mui/icons-material/GridView"; import FolderIcon from "@mui/icons-material/Folder"; @@ -148,7 +146,6 @@ const TopControls = ({ onFileSelect, onFileRemove, }: TopControlsProps) => { - const { isRainbowMode } = useRainbowThemeContext(); const [switchingTo, setSwitchingTo] = useState(null); const pageEditorState = usePageEditorDropdownState(); @@ -213,7 +210,6 @@ const TopControls = ({ onChange={handleViewChange} color="blue" fullWidth - className={isRainbowMode ? rainbowStyles.rainbowSegmentedControl : ""} style={{ transition: "all 0.2s ease", opacity: switchingTo ? 0.8 : 1, diff --git a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx index f6343f5ec5..d3383df0c7 100644 --- a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx @@ -19,8 +19,9 @@ import { import { useTranslation } from "react-i18next"; import { usePreferences } from "@app/contexts/PreferencesContext"; import { useAppConfig } from "@app/contexts/AppConfigContext"; -import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider"; +import { useTheme } from "@app/components/shared/ThemeProvider"; import LanguageSelector from "@app/components/shared/LanguageSelector"; +import type { ThemeMode } from "@app/constants/theme"; import type { ToolPanelMode } from "@app/constants/toolPanel"; import type { StartupView, @@ -85,7 +86,7 @@ const GeneralSection: React.FC = ({ const { t } = useTranslation(); const { preferences, updatePreference } = usePreferences(); const { config } = useAppConfig(); - const { toggleTheme, themeMode } = useRainbowThemeContext(); + const { setTheme, themeMode } = useTheme(); const [fileLimitInput, setFileLimitInput] = useState( preferences.autoUnzipFileLimit, ); @@ -499,15 +500,13 @@ const GeneralSection: React.FC = ({ {t( "settings.general.themeDescription", - "Switch between light and dark mode", + "Choose light, dark, or follow your system", )}
{ - if ((themeMode === "dark") !== (val === "dark")) toggleTheme(); - }} + value={themeMode} + onChange={(val) => setTheme(val as ThemeMode)} data={[ { label: t("settings.general.themeLight", "Light"), @@ -517,6 +516,10 @@ const GeneralSection: React.FC = ({ label: t("settings.general.themeDark", "Dark"), value: "dark", }, + { + label: t("settings.general.themeSystem", "System"), + value: "system", + }, ]} />
diff --git a/frontend/editor/src/core/components/shared/quickAccessBar/QuickAccessBar.css b/frontend/editor/src/core/components/shared/quickAccessBar/QuickAccessBar.css index dde53c3c47..d5f9592bfd 100644 --- a/frontend/editor/src/core/components/shared/quickAccessBar/QuickAccessBar.css +++ b/frontend/editor/src/core/components/shared/quickAccessBar/QuickAccessBar.css @@ -51,23 +51,8 @@ direction: ltr; /* keep layout stable when document is rtl */ } -/* Rainbow mode container */ -.quick-access-bar-main.rainbow-mode { - background-color: var(--bg-muted); - width: 4.5rem; - min-width: 4.5rem; - max-width: 4.5rem; - position: relative; - z-index: 10; - border-right: 1px solid var(--border-default); - flex-shrink: 0; - box-sizing: border-box; - direction: ltr; -} - /* RTL adjustments keep the bar on-screen and separated from content */ -:root[dir="rtl"] .quick-access-bar-main, -:root[dir="rtl"] .quick-access-bar-main.rainbow-mode { +:root[dir="rtl"] .quick-access-bar-main { border-right: none; border-left: 1px solid var(--border-default); } diff --git a/frontend/editor/src/core/components/tools/RightSidebar.tsx b/frontend/editor/src/core/components/tools/RightSidebar.tsx index 795b6082ed..494553a690 100644 --- a/frontend/editor/src/core/components/tools/RightSidebar.tsx +++ b/frontend/editor/src/core/components/tools/RightSidebar.tsx @@ -1,10 +1,8 @@ import { useMemo, useState } from "react"; import { ActionIcon } from "@mantine/core"; import { useTranslation } from "react-i18next"; -import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider"; import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; import { useSidebarContext } from "@app/contexts/SidebarContext"; -import rainbowStyles from "@app/styles/rainbow.module.css"; import { useIsMobile } from "@app/hooks/useIsMobile"; import ToolPanel from "@app/components/tools/ToolPanel"; import ToolSearch from "@app/components/tools/toolPicker/ToolSearch"; @@ -43,7 +41,6 @@ import "@app/components/tools/ToolPanel.css"; */ export default function RightSidebar() { const { t } = useTranslation(); - const { isRainbowMode } = useRainbowThemeContext(); const { sidebarRefs } = useSidebarContext(); const { toolPanelRef, quickAccessRef } = sidebarRefs; const isMobile = useIsMobile(); @@ -207,9 +204,7 @@ export default function RightSidebar() { ref={toolPanelRef} data-sidebar="tool-panel" data-tour={fullscreenExpanded ? undefined : "tool-panel"} - className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${ - isRainbowMode ? rainbowStyles.rainbowPaper : "" - } ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`} + className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`} style={{ width: computedWidth(), padding: "0", diff --git a/frontend/editor/src/core/constants/theme.ts b/frontend/editor/src/core/constants/theme.ts index 2de5b145cb..b9263772c9 100644 --- a/frontend/editor/src/core/constants/theme.ts +++ b/frontend/editor/src/core/constants/theme.ts @@ -1,10 +1,35 @@ // Theme constants and utilities -export type ThemeMode = "light" | "dark" | "rainbow"; +// Stored theme preference. "system" follows the OS. +export type ThemeMode = "light" | "dark" | "system"; -// Detect OS theme preference -export function getSystemTheme(): "light" | "dark" { - return window?.matchMedia?.("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; +// The concrete scheme applied to the UI. +export type ColorScheme = "light" | "dark"; + +// Detect the OS theme preference. Never throws: if the environment can't be +// read (no window, no matchMedia, or it errors), defaults to "light". +export function getSystemTheme(): ColorScheme { + try { + if ( + typeof window === "undefined" || + typeof window.matchMedia !== "function" + ) { + return "light"; + } + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + } catch { + return "light"; + } +} + +// Resolve a theme preference to a concrete light/dark scheme. +// Falls back to systemScheme for unrecognised values (e.g. stale "rainbow"). +export function resolveColorScheme( + mode: ThemeMode, + systemScheme: ColorScheme, +): ColorScheme { + if (mode === "light" || mode === "dark") return mode; + return systemScheme; } diff --git a/frontend/editor/src/core/hooks/useCookieConsent.ts b/frontend/editor/src/core/hooks/useCookieConsent.ts index 8c8e38f482..b4b0296f05 100644 --- a/frontend/editor/src/core/hooks/useCookieConsent.ts +++ b/frontend/editor/src/core/hooks/useCookieConsent.ts @@ -1,6 +1,7 @@ import { useEffect, useState, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { BASE_PATH } from "@app/constants/app"; +import { getSystemTheme } from "@app/constants/theme"; import { useAppConfig } from "@app/contexts/AppConfigContext"; import { Z_INDEX_COOKIE_CONSENT_BANNER, @@ -26,7 +27,6 @@ declare global { interface CookieConsentConfig { analyticsEnabled?: boolean; - forceLightMode?: boolean; } // Shard so Mantine's scroll-lock doesn't swallow events on the consent dialog; @@ -41,7 +41,6 @@ export const COOKIE_CONSENT_SCROLL_SHARD = { export const useCookieConsent = ({ analyticsEnabled = false, - forceLightMode = false, }: CookieConsentConfig = {}) => { const { t } = useTranslation(); const { config } = useAppConfig(); @@ -75,9 +74,6 @@ export const useCookieConsent = ({ } if (window.CookieConsent) { - if (forceLightMode) { - document.documentElement.classList.remove("cc--darkmode"); - } setIsInitialized(true); return; } @@ -87,11 +83,6 @@ export const useCookieConsent = ({ script.onload = () => { setTimeout(() => { const detectTheme = () => { - if (forceLightMode) { - document.documentElement.classList.remove("cc--darkmode"); - return false; - } - const mantineScheme = document.documentElement.getAttribute( "data-mantine-color-scheme", ); @@ -99,9 +90,7 @@ export const useCookieConsent = ({ document.documentElement.classList.contains("light"); const hasDarkClass = document.documentElement.classList.contains("dark"); - const systemPrefersDark = window.matchMedia( - "(prefers-color-scheme: dark)", - ).matches; + const systemPrefersDark = getSystemTheme() === "dark"; let isDarkMode: boolean; if (mantineScheme) { @@ -125,24 +114,22 @@ export const useCookieConsent = ({ return; } - if (!forceLightMode) { - const themeObserver = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if ( - mutation.type === "attributes" && - (mutation.attributeName === "data-mantine-color-scheme" || - mutation.attributeName === "class") - ) { - detectTheme(); - } - }); + const themeObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if ( + mutation.type === "attributes" && + (mutation.attributeName === "data-mantine-color-scheme" || + mutation.attributeName === "class") + ) { + detectTheme(); + } }); + }); - themeObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ["data-mantine-color-scheme", "class"], - }); - } + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-mantine-color-scheme", "class"], + }); try { const overrides = getCookieConsentOverrides(); @@ -319,32 +306,19 @@ export const useCookieConsent = ({ document.head.removeChild(customCSS); } }; - }, [ - analyticsEnabled, - config?.enablePosthog, - config?.enableScarf, - t, - forceLightMode, - ]); + }, [analyticsEnabled, config?.enablePosthog, config?.enableScarf, t]); useEffect(() => { if (!isInitialized) return; const detectTheme = () => { - if (forceLightMode) { - document.documentElement.classList.remove("cc--darkmode"); - return false; - } - const mantineScheme = document.documentElement.getAttribute( "data-mantine-color-scheme", ); const hasLightClass = document.documentElement.classList.contains("light"); const hasDarkClass = document.documentElement.classList.contains("dark"); - const systemPrefersDark = window.matchMedia( - "(prefers-color-scheme: dark)", - ).matches; + const systemPrefersDark = getSystemTheme() === "dark"; let isDarkMode: boolean; if (mantineScheme) { @@ -363,32 +337,25 @@ export const useCookieConsent = ({ detectTheme(); - let themeObserver: MutationObserver | null = null; - if (!forceLightMode) { - themeObserver = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if ( - mutation.type === "attributes" && - (mutation.attributeName === "data-mantine-color-scheme" || - mutation.attributeName === "class") - ) { - detectTheme(); - } - }); + const themeObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if ( + mutation.type === "attributes" && + (mutation.attributeName === "data-mantine-color-scheme" || + mutation.attributeName === "class") + ) { + detectTheme(); + } }); + }); - themeObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ["data-mantine-color-scheme", "class"], - }); - } + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-mantine-color-scheme", "class"], + }); - return () => { - if (themeObserver) { - themeObserver.disconnect(); - } - }; - }, [forceLightMode, isInitialized]); + return () => themeObserver.disconnect(); + }, [isInitialized]); useEffect(() => { if (!isInitialized || !window.CookieConsent) return; diff --git a/frontend/editor/src/core/hooks/useRainbowTheme.ts b/frontend/editor/src/core/hooks/useRainbowTheme.ts deleted file mode 100644 index 1241f234d1..0000000000 --- a/frontend/editor/src/core/hooks/useRainbowTheme.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { useCallback, useRef, useEffect } from "react"; -import { usePreferences } from "@app/contexts/PreferencesContext"; -import type { ThemeMode } from "@app/constants/theme"; - -interface RainbowThemeHook { - themeMode: ThemeMode; - isRainbowMode: boolean; - isToggleDisabled: boolean; - toggleTheme: () => void; - activateRainbow: () => void; - deactivateRainbow: () => void; -} - -const allowRainbowMode = false; // Override to allow/disallow fun - -export function useRainbowTheme(): RainbowThemeHook { - const { preferences, updatePreference } = usePreferences(); - const themeMode = preferences.theme; - - // Track rapid toggles for easter egg - const toggleCount = useRef(0); - const lastToggleTime = useRef(Date.now()); - const isToggleDisabled = useRef(false); - - // Apply rainbow class to body whenever theme changes - useEffect(() => { - if (themeMode === "rainbow") { - document.body.classList.add("rainbow-mode-active"); - showRainbowNotification(); - } else { - document.body.classList.remove("rainbow-mode-active"); - } - }, [themeMode]); - - const showRainbowNotification = () => { - // Remove any existing notification - const existingNotification = document.getElementById( - "rainbow-notification", - ); - if (existingNotification) { - existingNotification.remove(); - } - - // Create and show rainbow notification - const notification = document.createElement("div"); - notification.id = "rainbow-notification"; - notification.innerHTML = - "🌈 RAINBOW MODE ACTIVATED! 🌈
Button disabled for 3 seconds, then click to exit"; - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: linear-gradient(45deg, #ff0000, #ff8800, #ffff00, #88ff00, #00ff88, #00ffff, #0088ff, #8800ff); - background-size: 300% 300%; - animation: rainbowBackground 1s ease infinite; - color: white; - padding: 15px 20px; - border-radius: 25px; - font-weight: bold; - font-size: 16px; - z-index: 1000; - border: 2px solid white; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.8); - user-select: none; - pointer-events: none; - transition: opacity 0.3s ease; - `; - - document.body.appendChild(notification); - - // Auto-remove notification after 3 seconds - setTimeout(() => { - if (notification) { - notification.style.opacity = "0"; - setTimeout(() => { - if (notification.parentNode) { - notification.parentNode.removeChild(notification); - } - }, 300); - } - }, 3000); - }; - - const showExitNotification = () => { - // Remove any existing notification - const existingNotification = document.getElementById( - "rainbow-exit-notification", - ); - if (existingNotification) { - existingNotification.remove(); - } - - // Create and show exit notification - const notification = document.createElement("div"); - notification.id = "rainbow-exit-notification"; - notification.innerHTML = "🌙 Rainbow mode deactivated"; - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: linear-gradient(45deg, #333, #666); - color: white; - padding: 15px 20px; - border-radius: 25px; - font-weight: bold; - font-size: 16px; - z-index: 1000; - border: 2px solid #999; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); - user-select: none; - pointer-events: none; - transition: opacity 0.3s ease; - `; - - document.body.appendChild(notification); - - // Auto-remove notification after 2 seconds - setTimeout(() => { - if (notification) { - notification.style.opacity = "0"; - setTimeout(() => { - if (notification.parentNode) { - notification.parentNode.removeChild(notification); - } - }, 300); - } - }, 2000); - }; - - const toggleTheme = useCallback(() => { - // Don't allow toggle if disabled - if (isToggleDisabled.current) { - return; - } - - const currentTime = Date.now(); - - // Simple exit from rainbow mode with single click (after cooldown period) - if (themeMode === "rainbow") { - updatePreference("theme", "light"); - console.log("🌈 Rainbow mode deactivated. Thanks for trying it!"); - showExitNotification(); - return; - } - - // Reset counter if too much time has passed (2.5 seconds) - if (currentTime - lastToggleTime.current > 2500) { - toggleCount.current = 1; - } else { - toggleCount.current++; - } - lastToggleTime.current = currentTime; - - // Easter egg: Activate rainbow mode after 10 rapid toggles - if (allowRainbowMode && toggleCount.current >= 10) { - updatePreference("theme", "rainbow"); - console.log( - "🌈 RAINBOW MODE ACTIVATED! 🌈 You found the secret easter egg!", - ); - console.log( - "🌈 Button will be disabled for 3 seconds, then click once to exit!", - ); - - // Disable toggle for 3 seconds - isToggleDisabled.current = true; - setTimeout(() => { - isToggleDisabled.current = false; - console.log( - "🌈 Theme toggle re-enabled! Click once to exit rainbow mode.", - ); - }, 3000); - - // Reset counter - toggleCount.current = 0; - return; - } - - // Normal theme switching - const nextTheme = themeMode === "light" ? "dark" : "light"; - updatePreference("theme", nextTheme); - }, [themeMode, updatePreference]); - - const activateRainbow = useCallback(() => { - updatePreference("theme", "rainbow"); - console.log("🌈 Rainbow mode manually activated!"); - }, [updatePreference]); - - const deactivateRainbow = useCallback(() => { - if (themeMode === "rainbow") { - updatePreference("theme", "light"); - console.log("🌈 Rainbow mode manually deactivated."); - } - }, [themeMode, updatePreference]); - - return { - themeMode, - isRainbowMode: themeMode === "rainbow", - isToggleDisabled: isToggleDisabled.current, - toggleTheme, - activateRainbow, - deactivateRainbow, - }; -} diff --git a/frontend/editor/src/core/services/preferencesService.ts b/frontend/editor/src/core/services/preferencesService.ts index 94f7ea10e2..bba1eadc22 100644 --- a/frontend/editor/src/core/services/preferencesService.ts +++ b/frontend/editor/src/core/services/preferencesService.ts @@ -2,7 +2,7 @@ import { type ToolPanelMode, DEFAULT_TOOL_PANEL_MODE, } from "@app/constants/toolPanel"; -import { type ThemeMode, getSystemTheme } from "@app/constants/theme"; +import { type ThemeMode } from "@app/constants/theme"; export type LogoVariant = "modern" | "classic"; @@ -46,7 +46,7 @@ export const DEFAULT_PREFERENCES: UserPreferences = { defaultToolPanelMode: DEFAULT_TOOL_PANEL_MODE, defaultStartupView: "tools", defaultViewerZoom: "auto", - theme: getSystemTheme(), + theme: "system", toolPanelModePromptSeen: false, hasSelectedToolPanelMode: false, showLegacyToolDescriptions: false, diff --git a/frontend/editor/src/core/styles/rainbow.module.css b/frontend/editor/src/core/styles/rainbow.module.css deleted file mode 100644 index 04ebde1990..0000000000 --- a/frontend/editor/src/core/styles/rainbow.module.css +++ /dev/null @@ -1,345 +0,0 @@ -/* Rainbow Mode Styles - Easter Egg! */ -@keyframes rainbowBackground { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } -} - -@keyframes rainbowBorder { - 0% { - border-color: #ff0000; - box-shadow: 0 0 15px #ff0000; - } - 14% { - border-color: #ff8800; - box-shadow: 0 0 15px #ff8800; - } - 28% { - border-color: #ffff00; - box-shadow: 0 0 15px #ffff00; - } - 42% { - border-color: #88ff00; - box-shadow: 0 0 15px #88ff00; - } - 57% { - border-color: #00ff88; - box-shadow: 0 0 15px #00ff88; - } - 71% { - border-color: #0088ff; - box-shadow: 0 0 15px #0088ff; - } - 85% { - border-color: #8800ff; - box-shadow: 0 0 15px #8800ff; - } - 100% { - border-color: #ff0000; - box-shadow: 0 0 15px #ff0000; - } -} - -@keyframes rainbowText { - 0% { - color: #ff0000; - text-shadow: 0 0 10px #ff0000; - } - 14% { - color: #ff8800; - text-shadow: 0 0 10px #ff8800; - } - 28% { - color: #ffff00; - text-shadow: 0 0 10px #ffff00; - } - 42% { - color: #88ff00; - text-shadow: 0 0 10px #88ff00; - } - 57% { - color: #00ff88; - text-shadow: 0 0 10px #00ff88; - } - 71% { - color: #0088ff; - text-shadow: 0 0 10px #0088ff; - } - 85% { - color: #8800ff; - text-shadow: 0 0 10px #8800ff; - } - 100% { - color: #ff0000; - text-shadow: 0 0 10px #ff0000; - } -} - -@keyframes rainbowPulse { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.05); - } -} - -/* Main rainbow theme class */ -.rainbowMode { - background: linear-gradient( - -45deg, - #ff0000, - #ff8800, - #ffff00, - #88ff00, - #00ff88, - #00ffff, - #0088ff, - #8800ff, - #ff0088, - #ff0000 - ) !important; - background-size: 400% 400% !important; - animation: rainbowBackground 3s ease infinite !important; - color: white !important; - overflow-x: hidden; -} - -/* Rainbow components */ -.rainbowCard { - background: linear-gradient( - 45deg, - #ff0000, - #ff8800, - #ffff00, - #88ff00, - #00ff88, - #00ffff, - #0088ff, - #8800ff, - #ff0088 - ) !important; - background-size: 400% 400% !important; - animation: - rainbowBackground 4s ease infinite, - rainbowBorder 2s linear infinite !important; - color: white !important; - border: 2px solid !important; - border-radius: 15px !important; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.3) !important; -} - -.rainbowButton { - background: linear-gradient( - 45deg, - #ff0000, - #ff8800, - #ffff00, - #88ff00, - #00ff88, - #00ffff, - #0088ff, - #8800ff, - #ff0088 - ) !important; - background-size: 300% 300% !important; - animation: - rainbowBackground 2s ease infinite, - rainbowBorder 1s linear infinite !important; - border: 2px solid !important; - color: white !important; - border-radius: 8px !important; - transition: all 0.3s ease !important; - font-weight: bold !important; -} - -.rainbowButton:hover { - transform: scale(1.05) !important; - animation: - rainbowBackground 1s ease infinite, - rainbowBorder 0.5s linear infinite, - rainbowPulse 0.5s ease infinite !important; - box-shadow: 0 0 25px rgba(255, 255, 255, 0.6) !important; -} - -.rainbowInput { - background: linear-gradient( - 90deg, - #ff0000, - #ff8800, - #ffff00, - #88ff00, - #00ff88, - #00ffff, - #0088ff, - #8800ff, - #ff0088 - ) !important; - background-size: 300% 300% !important; - animation: - rainbowBackground 2.5s ease infinite, - rainbowBorder 1.5s linear infinite !important; - border: 2px solid !important; - color: white !important; - border-radius: 8px !important; -} - -.rainbowInput::placeholder { - color: rgba(255, 255, 255, 0.8) !important; -} - -.rainbowText { - animation: rainbowText 3s linear infinite !important; - font-weight: bold !important; - text-shadow: 0 0 10px currentColor !important; -} - -.rainbowSegmentedControl { - background: linear-gradient( - 45deg, - #ff0000, - #ff8800, - #ffff00, - #88ff00, - #00ff88, - #00ffff, - #0088ff, - #8800ff, - #ff0088 - ) !important; - background-size: 400% 400% !important; - animation: - rainbowBackground 3s ease infinite, - rainbowBorder 2s linear infinite !important; - border: 2px solid !important; - border-radius: 12px !important; - padding: 4px !important; -} - -.rainbowPaper { - background: linear-gradient( - 90deg, - #00ffff, - #0088ff, - #8800ff, - #ff0088 - ) !important; - background-size: 100% 100% !important; - animation: - rainbowBackground 3.5s ease infinite, - rainbowBorder 2s linear infinite !important; - border: 2px solid !important; - color: white !important; - border-radius: 12px !important; -} - -/* Easter egg notification */ -.rainbowNotification { - position: fixed !important; - top: 20px !important; - right: 20px !important; - background: linear-gradient( - 45deg, - #ffff00, - #88ff00, - #00ff88, - #00ffff - ) !important; - background-size: 300% 300% !important; - animation: - rainbowBackground 1s ease infinite, - rainbowBorder 0.5s linear infinite !important; - color: white !important; - padding: 15px 20px !important; - border-radius: 25px !important; - font-weight: bold !important; - font-size: 16px !important; - z-index: 10000 !important; - border: 2px solid white !important; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.8) !important; - user-select: none !important; - pointer-events: none !important; -} - -/* Specific component overrides */ -.rainbowMode [data-mantine-color-scheme] { - background: linear-gradient( - -45deg, - #ff0000, - #ff8800, - #ffff00, - #88ff00, - #00ff88, - #00ffff, - #0088ff, - #8800ff, - #ff0088, - #ff0000 - ) !important; - background-size: 400% 400% !important; - animation: rainbowBackground 3s ease infinite !important; -} - -/* Make all buttons rainbow in rainbow mode */ -.rainbowMode button { - background: linear-gradient( - 45deg, - #ffff00, - #88ff00, - #00ff88, - #00ffff - ) !important; - background-size: 100% 100% !important; - animation: rainbowBackground 2s ease infinite !important; - border: 2px solid !important; - animation: - rainbowBackground 2s ease infinite, - rainbowBorder 1.5s linear infinite !important; - color: white !important; - font-weight: bold !important; -} - -/* Make all inputs rainbow in rainbow mode */ -.rainbowMode input, -.rainbowMode select, -.rainbowMode textarea { - background: linear-gradient( - 90deg, - #ffff00, - #88ff00, - #00ff88, - #00ffff - ) !important; - background-size: 100% 100% !important; - animation: rainbowBackground 2.5s ease infinite !important; - border: 2px solid !important; - animation: - rainbowBackground 2.5s ease infinite, - rainbowBorder 1.5s linear infinite !important; - color: white !important; -} - -/* Rainbow text class */ -.rainbowText { - animation: rainbowText 3s linear infinite !important; - font-weight: bold !important; - text-shadow: 0 0 10px currentColor !important; -} - -/* Make all text rainbow */ -.rainbowMode h1, -.rainbowMode h2, -.rainbowMode h3, -.rainbowMode h4, -.rainbowMode h5, -.rainbowMode h6 { - animation: rainbowText 3s linear infinite !important; - font-weight: bold !important; -} diff --git a/frontend/editor/src/core/styles/zIndex.ts b/frontend/editor/src/core/styles/zIndex.ts index b771283568..3bc0801dee 100644 --- a/frontend/editor/src/core/styles/zIndex.ts +++ b/frontend/editor/src/core/styles/zIndex.ts @@ -43,7 +43,6 @@ export const Z_INDEX_SIGN_IN_MODAL = 9000; // Floating viewer menus rendered through document.body portals. export const Z_INDEX_VIEWER_FLOATING_MENU = 10000; -// Toast notifications and error displays - Always on top (higher than rainbow theme at 10000) export const Z_INDEX_TOAST = 10001; // Signature preview overlays inside the PDF viewer diff --git a/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx b/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx index 44d145ed0d..70876ca3ef 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx @@ -21,27 +21,6 @@ export const DesktopAuthLayout: React.FC = ({ [logoVariant, t], ); - // Force light mode on auth pages - useEffect(() => { - const htmlElement = document.documentElement; - const previousColorScheme = htmlElement.getAttribute( - "data-mantine-color-scheme", - ); - - // Set light mode - htmlElement.setAttribute("data-mantine-color-scheme", "light"); - - // Cleanup: restore previous theme when leaving auth pages - return () => { - if (previousColorScheme) { - htmlElement.setAttribute( - "data-mantine-color-scheme", - previousColorScheme, - ); - } - }; - }, []); - useEffect(() => { const update = () => { // Use viewport to avoid hysteresis when the card is already in single-column mode diff --git a/frontend/editor/src/desktop/components/SetupWizard/desktopOAuth.css b/frontend/editor/src/desktop/components/SetupWizard/desktopOAuth.css index 20cea0c5cb..979481bd45 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/desktopOAuth.css +++ b/frontend/editor/src/desktop/components/SetupWizard/desktopOAuth.css @@ -14,7 +14,7 @@ align-items: center; justify-content: flex-start; padding: 0.75rem 1rem; /* 12px 16px */ - border: 1px solid #d1d5db; + border: 1px solid var(--auth-input-border-light-only); border-radius: 0.75rem; /* 12px */ background-color: var(--auth-card-bg-light-only); font-size: 1rem; /* 16px */ @@ -27,7 +27,7 @@ } .oauth-button-vertical-desktop:hover:not(:disabled) { - background-color: #f3f4f6; + background-color: var(--bg-raised); } .oauth-button-vertical-desktop:disabled { diff --git a/frontend/editor/src/proprietary/App.tsx b/frontend/editor/src/proprietary/App.tsx index 4bdfb2ca45..8373391b5a 100644 --- a/frontend/editor/src/proprietary/App.tsx +++ b/frontend/editor/src/proprietary/App.tsx @@ -4,7 +4,7 @@ import { AppProviders } from "@app/components/AppProviders"; import { AppLayout } from "@app/components/AppLayout"; import { LoadingFallback } from "@app/components/shared/LoadingFallback"; import { PreferencesProvider } from "@app/contexts/PreferencesContext"; -import { RainbowThemeProvider } from "@app/components/shared/RainbowThemeProvider"; +import { ThemeProvider } from "@app/components/shared/ThemeProvider"; import Landing from "@app/routes/Landing"; import Login from "@app/routes/Login"; import Signup from "@app/routes/Signup"; @@ -31,7 +31,7 @@ import "@app/utils/fileIdSafety"; function PublicRouteProviders({ children }: { children: React.ReactNode }) { return ( - {children} + {children} ); } diff --git a/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx b/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx index 5541284f68..34d404d24c 100644 --- a/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx +++ b/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx @@ -1,5 +1,10 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react"; -import { createTheme, MantineProvider, Popover } from "@mantine/core"; +import { + createTheme, + MantineProvider, + Popover, + useMantineColorScheme, +} from "@mantine/core"; import { Rnd } from "react-rnd"; import { useTranslation } from "react-i18next"; import { ChatFABButton } from "@shared/components/ChatFABButton"; @@ -41,6 +46,12 @@ export function ChatFAB() { // from the local app-config. Either way the AI engine drives FAB visibility. const enabled = useAiEngineEnabled(); + // Scope the panel's nested MantineProvider to this ref; unscoped it writes + // its color scheme onto and overrides the whole app's theme. + const panelThemeRootRef = useRef(null); + const { colorScheme } = useMantineColorScheme(); + const panelColorScheme = colorScheme === "dark" ? "dark" : "light"; + // Detect loading → done transition. If the FAB is closed when the agent // finishes, show the tick badge until the user opens the panel. const isOpenRef = useRef(isOpen); @@ -221,11 +232,17 @@ export function ChatFAB() { }} > - - setIsOpen(false)} - backLabel={t("chat.fab.close", "Close chat")} - /> + panelThemeRootRef.current ?? undefined} + forceColorScheme={panelColorScheme} + > +
+ setIsOpen(false)} + backLabel={t("chat.fab.close", "Close chat")} + /> +
diff --git a/frontend/editor/src/proprietary/components/policies/README.md b/frontend/editor/src/proprietary/components/policies/README.md index df6535ba16..22ec6240b6 100644 --- a/frontend/editor/src/proprietary/components/policies/README.md +++ b/frontend/editor/src/proprietary/components/policies/README.md @@ -44,7 +44,7 @@ accent / `PanelHeader` icon slot / `Checkbox` leadingIcon / `MetricCard size`) were **built up in SUI** as part of this work — each has a Storybook story. Bootstrapping: the editor loads `@shared/tokens/tokens.css` globally via -`RainbowThemeProvider`, which also mirrors the Mantine colour scheme onto +`ThemeProvider`, which also mirrors the Mantine colour scheme onto `` (SUI's dark palette keys on `data-theme`). A global `@shared` alias in `editor/vite.config.ts` + `vitest.config.ts` resolves the shared components' own `@shared/*.css` self-imports. diff --git a/frontend/editor/src/proprietary/routes/Landing.tsx b/frontend/editor/src/proprietary/routes/Landing.tsx index da23926a44..91b79bbf8a 100644 --- a/frontend/editor/src/proprietary/routes/Landing.tsx +++ b/frontend/editor/src/proprietary/routes/Landing.tsx @@ -143,10 +143,11 @@ export default function Landing() { border: "1px solid rgba(37, 99, 235, 0.2)", }} > -

- {t("backendStartup.unreachable")} +

+ {t( + "backendStartup.unreachable", + "The application cannot currently connect to the backend. Verify the backend status and network connectivity, then try again.", + )}