import { useState, useEffect } from 'react'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Palette, Moon, Sun, Type } from 'lucide-react'; import { darkThemes, lightThemes, type Theme } from '@/config/theme-options'; import { UI_SANS_FONT_OPTIONS, UI_MONO_FONT_OPTIONS, DEFAULT_FONT_VALUE, } from '@/config/ui-font-options'; import { cn } from '@/lib/utils'; import { useAppStore } from '@/store/app-store'; import type { Project } from '@/lib/electron'; interface ProjectThemeSectionProps { project: Project; } export function ProjectThemeSection({ project }: ProjectThemeSectionProps) { const { theme: globalTheme, fontFamilySans: globalFontSans, fontFamilyMono: globalFontMono, setProjectTheme, setProjectFontSans, setProjectFontMono, } = useAppStore(); // Theme state const projectTheme = project.theme as Theme | undefined; const hasCustomTheme = projectTheme !== undefined; const effectiveTheme = projectTheme || globalTheme; // Determine if current theme is light or dark const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme); const [activeTab, setActiveTab] = useState<'dark' | 'light'>(isLightTheme ? 'light' : 'dark'); // Helper to validate fonts against available options const isValidSansFont = (font?: string): boolean => !!font && UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font); const isValidMonoFont = (font?: string): boolean => !!font && UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font); // Helper to get initial font value with validation const getInitialFontValue = (font: string | undefined, validator: (f?: string) => boolean) => font && validator(font) ? font : DEFAULT_FONT_VALUE; // Font local state - tracks what's selected when using custom fonts // Falls back to default if stored font is not in available options const [fontSansLocal, setFontSansLocal] = useState(() => getInitialFontValue(project.fontFamilySans, isValidSansFont) ); const [fontMonoLocal, setFontMonoLocal] = useState(() => getInitialFontValue(project.fontFamilyMono, isValidMonoFont) ); // Sync state when project changes useEffect(() => { setFontSansLocal(getInitialFontValue(project.fontFamilySans, isValidSansFont)); setFontMonoLocal(getInitialFontValue(project.fontFamilyMono, isValidMonoFont)); // Also sync the active tab based on current theme const currentIsLight = lightThemes.some((t) => t.value === (project.theme || globalTheme)); setActiveTab(currentIsLight ? 'light' : 'dark'); }, [project, globalTheme]); // Font state - check if project has custom fonts set const hasCustomFontSans = project.fontFamilySans !== undefined; const hasCustomFontMono = project.fontFamilyMono !== undefined; const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes; // Theme handlers const handleThemeChange = (theme: Theme) => { setProjectTheme(project.id, theme); }; const handleUseGlobalTheme = (checked: boolean) => { if (checked) { setProjectTheme(project.id, null); } else { setProjectTheme(project.id, globalTheme); } }; // Font handlers const handleUseGlobalFontSans = (checked: boolean) => { if (checked) { // Clear project font to use global setProjectFontSans(project.id, null); setFontSansLocal(DEFAULT_FONT_VALUE); } else { // Set explicit project override - use 'default' value to indicate explicit default choice const fontToSet = globalFontSans || DEFAULT_FONT_VALUE; setFontSansLocal(fontToSet); // Store the actual value (including 'default') so hasCustomFontSans stays true setProjectFontSans(project.id, fontToSet); } }; const handleUseGlobalFontMono = (checked: boolean) => { if (checked) { // Clear project font to use global setProjectFontMono(project.id, null); setFontMonoLocal(DEFAULT_FONT_VALUE); } else { // Set explicit project override - use 'default' value to indicate explicit default choice const fontToSet = globalFontMono || DEFAULT_FONT_VALUE; setFontMonoLocal(fontToSet); // Store the actual value (including 'default') so hasCustomFontMono stays true setProjectFontMono(project.id, fontToSet); } }; const handleFontSansChange = (value: string) => { setFontSansLocal(value); // Store the actual value (including 'default') - only null clears to use global setProjectFontSans(project.id, value); }; const handleFontMonoChange = (value: string) => { setFontMonoLocal(value); // Store the actual value (including 'default') - only null clears to use global setProjectFontMono(project.id, value); }; // Get display label for global font const getGlobalFontSansLabel = () => { if (!globalFontSans) return 'Default (Geist Sans)'; const option = UI_SANS_FONT_OPTIONS.find((o) => o.value === globalFontSans); return option?.label || globalFontSans; }; const getGlobalFontMonoLabel = () => { if (!globalFontMono) return 'Default (Geist Mono)'; const option = UI_MONO_FONT_OPTIONS.find((o) => o.value === globalFontMono); return option?.label || globalFontMono; }; return (

Theme & Fonts

Customize the appearance for this project.

{/* Use Global Theme Toggle */}

When enabled, this project will use the global theme setting. Disable to set a project-specific theme.

{/* Theme Selection - only show if not using global theme */} {hasCustomTheme && (
{/* Dark/Light Tabs */}
{themesToShow.map(({ value, label, Icon, testId, color }) => { const isActive = effectiveTheme === value; return ( ); })}
)} {/* Info when using global theme */} {!hasCustomTheme && (

This project is using the global theme:{' '} {globalTheme}

)} {/* Fonts Section */}
{/* UI Font */}
{!hasCustomFontSans && (

Currently using:{' '} {getGlobalFontSansLabel()}

)}
{hasCustomFontSans && (
)}
{/* Code Font */}
{!hasCustomFontMono && (

Currently using:{' '} {getGlobalFontMonoLabel()}

)}
{hasCustomFontMono && (
)}
); }