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%
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