160 lines
4.9 KiB
TypeScript
160 lines
4.9 KiB
TypeScript
import { writable, derived } from 'svelte/store';
|
|
import { browser } from '$app/environment';
|
|
import { type ThemeColorMap, defaultColorMap } from '$lib/components/tui/utils';
|
|
|
|
// Import theme JSON files
|
|
import archTheme from '$lib/assets/themes/arch.theme.json';
|
|
import catppuccinTheme from '$lib/assets/themes/catppuccin.theme.json';
|
|
import wintryTheme from '$lib/assets/themes/wintry.theme.json';
|
|
import roseTheme from '$lib/assets/themes/rose.theme.json';
|
|
import cerberusTheme from '$lib/assets/themes/cerberus.theme.json';
|
|
|
|
export type ColorTheme = 'arch' | 'catppuccin' | 'wintry' | 'rose' | 'cerberus';
|
|
export type Mode = 'dark' | 'light';
|
|
|
|
// Theme JSON structure
|
|
export interface ThemeJson {
|
|
name: string;
|
|
icon: string;
|
|
dark: {
|
|
colors: Record<string, string>;
|
|
colorMap: ThemeColorMap;
|
|
};
|
|
light: {
|
|
colors: Record<string, string>;
|
|
colorMap: ThemeColorMap;
|
|
};
|
|
}
|
|
|
|
export interface ThemeColors {
|
|
primary: string;
|
|
secondary: string;
|
|
accent: string;
|
|
background: string;
|
|
backgroundLight: string;
|
|
text: string;
|
|
textMuted: string;
|
|
border: string;
|
|
terminal: string;
|
|
terminalPrompt: string;
|
|
terminalUser: string;
|
|
terminalPath: string;
|
|
colorMap: ThemeColorMap;
|
|
}
|
|
|
|
// Load themes from JSON files
|
|
const themes: Record<ColorTheme, ThemeJson> = {
|
|
arch: archTheme as ThemeJson,
|
|
catppuccin: catppuccinTheme as ThemeJson,
|
|
wintry: wintryTheme as ThemeJson,
|
|
rose: roseTheme as ThemeJson,
|
|
cerberus: cerberusTheme as ThemeJson
|
|
};
|
|
|
|
// Export themes for external access
|
|
export { themes };
|
|
|
|
// Build theme colors from JSON - merges colors into colorMap so formatter can use both
|
|
function buildThemeColors(theme: ThemeJson, mode: Mode): ThemeColors {
|
|
const modeData = theme[mode];
|
|
// Merge: defaults -> theme palette -> theme colors (so text, primary, etc. are correct hex values)
|
|
const mergedColorMap: ThemeColorMap = {
|
|
...defaultColorMap, // Fallback defaults (hex values)
|
|
...(modeData.colorMap ?? {}), // Theme's color palette (hex values)
|
|
// Also add theme colors so (&text), (&primary), etc. resolve to correct hex for this mode
|
|
'text': modeData.colors.text,
|
|
'textMuted': modeData.colors.textMuted,
|
|
'primary': modeData.colors.primary,
|
|
'secondary': modeData.colors.secondary,
|
|
'accent': modeData.colors.accent,
|
|
'background': modeData.colors.background,
|
|
'backgroundLight': modeData.colors.backgroundLight,
|
|
'border': modeData.colors.border,
|
|
'terminal': modeData.colors.terminal,
|
|
'terminalPrompt': modeData.colors.terminalPrompt,
|
|
'terminalUser': modeData.colors.terminalUser,
|
|
'terminalPath': modeData.colors.terminalPath,
|
|
'muted': modeData.colors.textMuted, // alias
|
|
};
|
|
return {
|
|
primary: modeData.colors.primary,
|
|
secondary: modeData.colors.secondary,
|
|
accent: modeData.colors.accent,
|
|
background: modeData.colors.background,
|
|
backgroundLight: modeData.colors.backgroundLight,
|
|
text: modeData.colors.text,
|
|
textMuted: modeData.colors.textMuted,
|
|
border: modeData.colors.border,
|
|
terminal: modeData.colors.terminal,
|
|
terminalPrompt: modeData.colors.terminalPrompt,
|
|
terminalUser: modeData.colors.terminalUser,
|
|
terminalPath: modeData.colors.terminalPath,
|
|
colorMap: mergedColorMap
|
|
};
|
|
}
|
|
|
|
// Build the theme colors map from JSON themes
|
|
const themeColorsMap: Record<ColorTheme, { dark: ThemeColors; light: ThemeColors }> =
|
|
Object.fromEntries(
|
|
Object.entries(themes).map(([key, theme]) => [
|
|
key,
|
|
{
|
|
dark: buildThemeColors(theme, 'dark'),
|
|
light: buildThemeColors(theme, 'light')
|
|
}
|
|
])
|
|
) as Record<ColorTheme, { dark: ThemeColors; light: ThemeColors }>;
|
|
|
|
function getInitialMode(): Mode {
|
|
if (browser) {
|
|
const stored = localStorage.getItem('mode');
|
|
if (stored === 'dark' || stored === 'light') return stored;
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
}
|
|
return 'dark';
|
|
}
|
|
|
|
function getInitialTheme(): ColorTheme {
|
|
if (browser) {
|
|
const stored = localStorage.getItem('colorTheme');
|
|
if (stored && stored in themeColorsMap) return stored as ColorTheme;
|
|
}
|
|
return 'arch';
|
|
}
|
|
|
|
export const mode = writable<Mode>(getInitialMode());
|
|
export const colorTheme = writable<ColorTheme>(getInitialTheme());
|
|
|
|
export const themeColors = derived([mode, colorTheme], ([$mode, $colorTheme]) => {
|
|
return themeColorsMap[$colorTheme][$mode];
|
|
});
|
|
|
|
// Subscribe to persist changes
|
|
if (browser) {
|
|
mode.subscribe((value) => {
|
|
localStorage.setItem('mode', value);
|
|
document.documentElement.setAttribute('data-mode', value);
|
|
});
|
|
|
|
colorTheme.subscribe((value) => {
|
|
localStorage.setItem('colorTheme', value);
|
|
document.documentElement.setAttribute('data-theme', value);
|
|
});
|
|
}
|
|
|
|
export function toggleMode() {
|
|
mode.update((m) => (m === 'dark' ? 'light' : 'dark'));
|
|
}
|
|
|
|
export function setColorTheme(theme: ColorTheme) {
|
|
colorTheme.set(theme);
|
|
}
|
|
|
|
// Generate theme options from loaded themes
|
|
export const themeOptions: { value: ColorTheme; label: string; icon: string }[] =
|
|
Object.entries(themes).map(([key, theme]) => ({
|
|
value: key as ColorTheme,
|
|
label: theme.name,
|
|
icon: theme.icon
|
|
}));
|