SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
4.5 KB · 150 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/components/theme-provider.tsx6 *7 *  Author:    Simon-Pierre Boucher8 *  Contact:   contact@spboucher.ai9 *  Website:   https://www.spboucher.ai10 *  Demo:      https://www.vquant.ai11 *  License:   MIT (see LICENSE)12 *13 *  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { createContext, useContext, useEffect, useState } from "react";1819type Theme = "dark" | "light";2021export type ThemeColor = "blue" | "green" | "purple" | "red" | "orange" | "cyan";2223type ThemeProviderProps = {24  children: React.ReactNode;25  defaultTheme?: Theme;26  storageKey?: string;27};2829type ThemeProviderState = {30  theme: Theme;31  setTheme: (theme: Theme) => void;32  toggleTheme: () => void;33  themeColor: ThemeColor;34  setThemeColor: (color: ThemeColor) => void;35};3637const initialState: ThemeProviderState = {38  theme: "light",39  setTheme: () => null,40  toggleTheme: () => null,41  themeColor: "orange",42  setThemeColor: () => null,43};4445const ThemeProviderContext = createContext<ThemeProviderState>(initialState);4647const themeColorMap: Record<ThemeColor, { hue: number; sat: number }> = {48  blue: { hue: 210, sat: 100 },49  green: { hue: 142, sat: 76 },50  purple: { hue: 255, sat: 85 },51  red: { hue: 0, sat: 84 },52  orange: { hue: 14, sat: 100 },53  cyan: { hue: 199, sat: 89 },54};5556export function ThemeProvider({57  children,58  defaultTheme = "light",59  storageKey = "stock-theme",60  ...props61}: ThemeProviderProps) {62  const [theme, setThemeState] = useState<Theme>(() => {63    try {64      return (localStorage.getItem(storageKey) as Theme) || defaultTheme;65    } catch {66      return defaultTheme;67    }68  });6970  const [themeColor, setThemeColorState] = useState<ThemeColor>(() => {71    try {72      return (localStorage.getItem(`${storageKey}-color-v2`) as ThemeColor) || "orange";73    } catch {74      return "purple";75    }76  });7778  useEffect(() => {79    const root = window.document.documentElement;80    root.classList.remove("light", "dark");81    root.classList.add(theme);82  }, [theme]);8384  useEffect(() => {85    const root = window.document.documentElement;86    const colorConfig = themeColorMap[themeColor];8788    // Update primary color variables89    if (theme === "light") {90      root.style.setProperty("--primary", `${colorConfig.hue} ${colorConfig.sat}% 50%`);91      root.style.setProperty("--ring", `${colorConfig.hue} ${colorConfig.sat}% 50%`);92      root.style.setProperty("--sidebar-primary", `${colorConfig.hue} ${colorConfig.sat}% 50%`);93      root.style.setProperty("--sidebar-ring", `${colorConfig.hue} ${colorConfig.sat}% 50%`);94    } else {95      root.style.setProperty("--primary", `${colorConfig.hue} ${colorConfig.sat}% 50%`);96      root.style.setProperty("--ring", `${colorConfig.hue} ${colorConfig.sat}% 60%`);97      root.style.setProperty("--sidebar-primary", `${colorConfig.hue} ${colorConfig.sat}% 60%`);98      root.style.setProperty("--sidebar-ring", `${colorConfig.hue} ${colorConfig.sat}% 60%`);99    }100  }, [themeColor, theme]);101102  const value = {103    theme,104    setTheme: (theme: Theme) => {105      try {106        localStorage.setItem(storageKey, theme);107        setThemeState(theme);108      } catch (error) {109        console.error("Failed to save theme:", error);110        setThemeState(theme);111      }112    },113    toggleTheme: () => {114      try {115        const newTheme = theme === "dark" ? "light" : "dark";116        localStorage.setItem(storageKey, newTheme);117        setThemeState(newTheme);118      } catch (error) {119        console.error("Failed to toggle theme:", error);120        setThemeState(theme === "dark" ? "light" : "dark");121      }122    },123    themeColor,124    setThemeColor: (color: ThemeColor) => {125      try {126        localStorage.setItem(`${storageKey}-color-v2`, color);127        setThemeColorState(color);128      } catch (error) {129        console.error("Failed to save theme color:", error);130        setThemeColorState(color);131      }132    },133  };134135  return (136    <ThemeProviderContext.Provider {...props} value={value}>137      {children}138    </ThemeProviderContext.Provider>139  );140}141142export const useTheme = () => {143  const context = useContext(ThemeProviderContext);144145  if (context === undefined)146    throw new Error("useTheme must be used within a ThemeProvider");147148  return context;149};150