Files
bookhoard/web/src/theme.ts
T
john-okeefe 3d3af8bd92 refactor(theme): remove wood themes from core theme system
- Remove wood-light, wood-dark, wood-mahogany from ThemeType
- Remove wood theme gradient logic from applyTheme()
- Wood themes will be reimplemented as separate paneling feature
- Paneling will target dashboard bookshelf background only
2026-02-24 12:57:54 -05:00

138 lines
3.9 KiB
TypeScript

// Theme management functionality
type ThemeType =
| 'tokyo-night'
| 'dracula'
| 'nord'
| 'solarized-dark'
| 'monokai'
| 'one-dark-pro'
| 'material-dark'
| 'catppuccin-mocha'
| 'catppuccin-macchiato'
| 'catppuccin-frappe'
| 'catppuccin-latte';
const DEFAULT_THEME: ThemeType = 'tokyo-night';
const THEME_STORAGE_KEY = 'theme';
const TOKEN_STORAGE_KEY = 'token';
// Apply theme to document body
const applyTheme = (theme: string): void => {
// Apply regular theme only
document.body.className = `theme-${theme}`;
document.body.style.background = '';
document.body.style.backgroundSize = '';
document.body.style.backgroundAttachment = '';
localStorage.setItem(THEME_STORAGE_KEY, theme);
};
// Load theme from localStorage or use default
const loadTheme = (): void => {
const storedTheme = localStorage.getItem(THEME_STORAGE_KEY) as ThemeType | null;
const theme = storedTheme || DEFAULT_THEME;
applyTheme(theme);
};
// Handle theme change from user selection
const changeTheme = async (): Promise<void> => {
const themeSelect = document.getElementById('theme-select') as HTMLSelectElement;
if (!themeSelect) return;
const theme = themeSelect.value as ThemeType;
applyTheme(theme);
// Save to server if logged in
const token = localStorage.getItem(TOKEN_STORAGE_KEY);
if (!token) return;
try {
const response = await fetch('/api/auth/theme', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ theme })
});
if (!response.ok) {
console.log('Theme save failed');
}
} catch (error) {
console.log('Theme save failed', error);
}
};
// Load user's theme from server if logged in
const loadUserTheme = async (): Promise<void> => {
const token = localStorage.getItem(TOKEN_STORAGE_KEY);
if (!token) return;
try {
const response = await fetch('/api/auth/profile', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const data = await response.json();
if (data.theme) {
applyTheme(data.theme as string);
}
}
} catch {
// Silently fail - user will get default theme
}
};
// Initialize theme system
const initializeTheme = (): void => {
loadTheme();
loadUserTheme();
// Set theme select value to current theme
const themeSelect = document.getElementById('theme-select') as HTMLSelectElement;
if (themeSelect) {
const currentTheme = localStorage.getItem(THEME_STORAGE_KEY) || DEFAULT_THEME;
themeSelect.value = currentTheme;
}
// Setup smooth scroll for anchor links
setupSmoothScroll();
};
// Setup smooth scrolling for anchor links
const setupSmoothScroll = (): void => {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', (e) => {
e.preventDefault();
const href = anchor.getAttribute('href');
if (!href) return;
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
};
// Auto-initialize when DOM is ready
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeTheme);
} else {
initializeTheme();
}
}
// Make changeTheme available globally for HTML onchange attribute
(window as any).changeTheme = changeTheme;
// Export applyTheme to window for use by header.ts
(window as any).applyTheme = applyTheme;