Koito/client/app/providers/ThemeProvider.tsx
Gabe Farrell c16b557c21
feat: v0.0.10 (#23)
* feat: single SOT for themes + basic custom support

* fix: adjust colors for yuu theme

* feat: Allow loading of environment variables from file (#20)

* feat: allow loading of environment variables from file

* Panic if a file for an environment variable cannot be read

* Use log.Fatalf + os.Exit instead of panic

* fix: remove supurfluous call to os.Exit()

---------

Co-authored-by: adaexec <nixos-git.s1pht@simplelogin.com>
Co-authored-by: Gabe Farrell <90876006+gabehf@users.noreply.github.com>

* chore: add pr test workflow

* chore: changelog

* feat: make all activity grids configurable

* fix: adjust activity grid style

* fix: make background gradient consistent size

* revert: remove year from activity grid opts

* style: adjust top item list min size to 200px

* feat: add support for custom themes

* fix: stabilized the order of top items

* chore: update changelog

* feat: native import & export

* fix: use correct request body for alias requests

* fix: clear input when closing edit modal

* chore: changelog

* docs: make endpoint clearer for some apps

* feat: add ui and handler for export

* fix: fix pr test workflow

---------

Co-authored-by: adaexec <78047743+adaexec@users.noreply.github.com>
Co-authored-by: adaexec <nixos-git.s1pht@simplelogin.com>
2025-06-18 08:48:19 -04:00

95 lines
2.7 KiB
TypeScript

import { createContext, useEffect, useState, useCallback, type ReactNode } from 'react';
import { type Theme } from '~/styles/themes.css';
import { themeVars } from '~/styles/vars.css';
interface ThemeContextValue {
theme: string;
setTheme: (theme: string) => void;
setCustomTheme: (theme: Theme) => void;
getCustomTheme: () => Theme | undefined;
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
function toKebabCase(str: string) {
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
}
function applyCustomThemeVars(theme: Theme) {
const root = document.documentElement;
for (const [key, value] of Object.entries(theme)) {
if (key === 'name') continue;
root.style.setProperty(`--color-${toKebabCase(key)}`, value);
}
}
function clearCustomThemeVars() {
for (const cssVar of Object.values(themeVars)) {
document.documentElement.style.removeProperty(cssVar);
}
}
export function ThemeProvider({
theme: initialTheme,
children,
}: {
theme: string;
children: ReactNode;
}) {
const [theme, setThemeName] = useState(initialTheme);
const setTheme = (theme: string) => {
setThemeName(theme)
}
const setCustomTheme = useCallback((customTheme: Theme) => {
localStorage.setItem('custom-theme', JSON.stringify(customTheme));
applyCustomThemeVars(customTheme);
setTheme('custom');
}, []);
const getCustomTheme = (): Theme | undefined => {
const themeStr = localStorage.getItem('custom-theme');
if (!themeStr) {
return undefined
}
try {
let theme = JSON.parse(themeStr) as Theme
return theme
} catch (err) {
return undefined
}
}
useEffect(() => {
const root = document.documentElement;
root.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme)
console.log(theme)
if (theme === 'custom') {
const saved = localStorage.getItem('custom-theme');
if (saved) {
try {
const parsed = JSON.parse(saved) as Theme;
applyCustomThemeVars(parsed);
} catch (err) {
console.error('Invalid custom theme in localStorage', err);
}
} else {
setTheme('yuu')
}
} else {
clearCustomThemeVars()
}
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme, setCustomTheme, getCustomTheme }}>
{children}
</ThemeContext.Provider>
);
}
export { ThemeContext };