import { lightTheme } from 'folds'; import { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { onDarkFontWeight, onLightFontWeight } from '../../config.css'; import { butterTheme, catppuccinMochaTheme, darkTheme, discordTheme, discordDarkerTheme, mochaTheme, silverTheme, stationeryDarkTheme, stationeryTheme, twilightTheme } from '../../colors.css'; import { settingsAtom } from '../state/settings'; import { useSetting } from '../state/hooks/settings'; import { pluginRegistry } from '../features/settings/plugins/PluginAPI'; export enum ThemeKind { Light = 'light', Dark = 'dark', } export type Theme = { id: string; kind: ThemeKind; classNames: string[]; }; export const isStationeryTheme = (theme: Theme | string): boolean => { const id = typeof theme === 'string' ? theme : theme.id; return id === 'stationery-theme' || id === 'stationery-dark-theme'; }; export const LightTheme: Theme = { id: 'light-theme', kind: ThemeKind.Light, classNames: ['light-theme', lightTheme, onLightFontWeight, 'prism-light'], }; export const SilverTheme: Theme = { id: 'silver-theme', kind: ThemeKind.Light, classNames: ['silver-theme', silverTheme, onLightFontWeight, 'prism-light'], }; export const DarkTheme: Theme = { id: 'dark-theme', kind: ThemeKind.Dark, classNames: ['dark-theme', darkTheme, onDarkFontWeight, 'prism-dark'], }; export const ButterTheme: Theme = { id: 'butter-theme', kind: ThemeKind.Dark, classNames: ['butter-theme', butterTheme, onDarkFontWeight, 'prism-dark'], }; export const DiscordTheme: Theme = { id: 'discord-theme', kind: ThemeKind.Dark, classNames: ['discord-theme', discordTheme, onDarkFontWeight, 'prism-dark'], }; export const DiscordDarkerTheme: Theme = { id: 'discord-darker-theme', kind: ThemeKind.Dark, classNames: ['discord-darker-theme', discordDarkerTheme, onDarkFontWeight, 'prism-dark'], }; export const TwilightTheme: Theme = { id: 'twilight-theme', kind: ThemeKind.Dark, classNames: ['twilight-theme', twilightTheme, onDarkFontWeight, 'prism-dark'], }; export const MochaTheme: Theme = { id: 'mocha-theme', kind: ThemeKind.Dark, classNames: ['mocha-theme', mochaTheme, onDarkFontWeight, 'prism-dark'], }; export const CatppuccinMochaTheme: Theme = { id: 'catppuccin-mocha-theme', kind: ThemeKind.Dark, classNames: ['catppuccin-mocha-theme', catppuccinMochaTheme, onDarkFontWeight, 'prism-dark'], }; export const StationeryTheme: Theme = { id: 'stationery-theme', kind: ThemeKind.Light, classNames: ['stationery-theme', 'stationery', stationeryTheme, onLightFontWeight, 'prism-light'], }; export const StationeryDarkTheme: Theme = { id: 'stationery-dark-theme', kind: ThemeKind.Dark, classNames: ['stationery-dark-theme', 'stationery', stationeryDarkTheme, onDarkFontWeight, 'prism-dark'], }; export const useThemes = (): Theme[] => { const [pluginThemesUpdate, setPluginThemesUpdate] = useState(0); // Force update when plugins change (this is a simple approach) useEffect(() => { const interval = setInterval(() => { setPluginThemesUpdate(prev => prev + 1); }, 1000); return () => clearInterval(interval); }, []); const builtInThemes: Theme[] = useMemo(() => [ LightTheme, SilverTheme, StationeryTheme, StationeryDarkTheme, DarkTheme, ButterTheme, DiscordTheme, DiscordDarkerTheme, TwilightTheme, MochaTheme, CatppuccinMochaTheme ], []); // Convert plugin themes to Theme format const pluginThemes: Theme[] = useMemo(() => { const themes = pluginRegistry.getPluginThemes(); return themes.map(pluginTheme => ({ id: pluginTheme.id, kind: pluginTheme.kind === 'dark' ? ThemeKind.Dark : ThemeKind.Light, classNames: [ pluginTheme.className, pluginTheme.kind === 'dark' ? onDarkFontWeight : onLightFontWeight, pluginTheme.kind === 'dark' ? 'prism-dark' : 'prism-light' ] })); }, [pluginThemesUpdate]); return [...builtInThemes, ...pluginThemes]; }; export const useThemeNames = (): Record => { const [pluginThemesUpdate, setPluginThemesUpdate] = useState(0); useEffect(() => { const interval = setInterval(() => { setPluginThemesUpdate(prev => prev + 1); }, 1000); return () => clearInterval(interval); }, []); return useMemo(() => { const builtInNames = { [LightTheme.id]: 'Light', [SilverTheme.id]: 'Silver', [StationeryTheme.id]: 'Stationery', [StationeryDarkTheme.id]: 'Stationery Dark', [DarkTheme.id]: 'Dark', [ButterTheme.id]: 'Butter', [DiscordTheme.id]: 'Discord', [DiscordDarkerTheme.id]: 'Discord Darker', [TwilightTheme.id]: 'Twilight', [MochaTheme.id]: 'Mocha', [CatppuccinMochaTheme.id]: 'Catppuccin Mocha', }; const pluginThemes = pluginRegistry.getPluginThemes(); const pluginNames = pluginThemes.reduce((acc, theme) => { acc[theme.id] = theme.name; return acc; }, {} as Record); return { ...builtInNames, ...pluginNames }; }, [pluginThemesUpdate]); }; export const useSystemThemeKind = (): ThemeKind => { const darkModeQueryList = useMemo(() => window.matchMedia('(prefers-color-scheme: dark)'), []); const [themeKind, setThemeKind] = useState( darkModeQueryList.matches ? ThemeKind.Dark : ThemeKind.Light ); useEffect(() => { const handleMediaQueryChange = () => { setThemeKind(darkModeQueryList.matches ? ThemeKind.Dark : ThemeKind.Light); }; darkModeQueryList.addEventListener('change', handleMediaQueryChange); return () => { darkModeQueryList.removeEventListener('change', handleMediaQueryChange); }; }, [darkModeQueryList, setThemeKind]); return themeKind; }; export const useActiveTheme = (): Theme => { const systemThemeKind = useSystemThemeKind(); const themes = useThemes(); const [systemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [themeId] = useSetting(settingsAtom, 'themeId'); const [lightThemeId] = useSetting(settingsAtom, 'lightThemeId'); const [darkThemeId] = useSetting(settingsAtom, 'darkThemeId'); if (!systemTheme) { const selectedTheme = themes.find((theme) => theme.id === themeId) ?? LightTheme; return selectedTheme; } const selectedTheme = systemThemeKind === ThemeKind.Dark ? themes.find((theme) => theme.id === darkThemeId) ?? DarkTheme : themes.find((theme) => theme.id === lightThemeId) ?? LightTheme; return selectedTheme; }; const ThemeContext = createContext(null); export const ThemeContextProvider = ThemeContext.Provider; export const useTheme = (): Theme => { const theme = useContext(ThemeContext); if (!theme) { throw new Error('No theme provided!'); } return theme; };