mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat: add global font settings with per-project override
- Add fontFamilySans and fontFamilyMono to GlobalSettings type - Add global font state and actions to app store - Update getEffectiveFontSans/Mono to fall back to global settings - Add font selectors to global Settings → Appearance - Add "Use Global Font" checkboxes in Project Settings → Theme - Add fonts to settings sync and migration - Include fonts in import/export JSON
This commit is contained in:
@@ -26,15 +26,19 @@ interface ProjectThemeSectionProps {
|
|||||||
export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
||||||
const {
|
const {
|
||||||
theme: globalTheme,
|
theme: globalTheme,
|
||||||
|
fontFamilySans: globalFontSans,
|
||||||
|
fontFamilyMono: globalFontMono,
|
||||||
setProjectTheme,
|
setProjectTheme,
|
||||||
setProjectFontSans,
|
setProjectFontSans,
|
||||||
setProjectFontMono,
|
setProjectFontMono,
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
||||||
const [fontSans, setFontSansLocal] = useState<string>(
|
|
||||||
|
// Font local state - tracks what's selected when using custom fonts
|
||||||
|
const [fontSansLocal, setFontSansLocal] = useState<string>(
|
||||||
project.fontFamilySans || DEFAULT_FONT_VALUE
|
project.fontFamilySans || DEFAULT_FONT_VALUE
|
||||||
);
|
);
|
||||||
const [fontMono, setFontMonoLocal] = useState<string>(
|
const [fontMonoLocal, setFontMonoLocal] = useState<string>(
|
||||||
project.fontFamilyMono || DEFAULT_FONT_VALUE
|
project.fontFamilyMono || DEFAULT_FONT_VALUE
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -44,38 +48,80 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
|||||||
setFontMonoLocal(project.fontFamilyMono || DEFAULT_FONT_VALUE);
|
setFontMonoLocal(project.fontFamilyMono || DEFAULT_FONT_VALUE);
|
||||||
}, [project]);
|
}, [project]);
|
||||||
|
|
||||||
|
// Theme state
|
||||||
const projectTheme = project.theme as Theme | undefined;
|
const projectTheme = project.theme as Theme | undefined;
|
||||||
const hasCustomTheme = projectTheme !== undefined;
|
const hasCustomTheme = projectTheme !== undefined;
|
||||||
const effectiveTheme = projectTheme || globalTheme;
|
const effectiveTheme = projectTheme || 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;
|
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
||||||
|
|
||||||
|
// Theme handlers
|
||||||
const handleThemeChange = (theme: Theme) => {
|
const handleThemeChange = (theme: Theme) => {
|
||||||
setProjectTheme(project.id, theme);
|
setProjectTheme(project.id, theme);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUseGlobalTheme = (checked: boolean) => {
|
const handleUseGlobalTheme = (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
// Clear project theme to use global
|
|
||||||
setProjectTheme(project.id, null);
|
setProjectTheme(project.id, null);
|
||||||
} else {
|
} else {
|
||||||
// Set project theme to current global theme
|
|
||||||
setProjectTheme(project.id, globalTheme);
|
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 to current global font or default
|
||||||
|
const fontToSet = globalFontSans || DEFAULT_FONT_VALUE;
|
||||||
|
setFontSansLocal(fontToSet);
|
||||||
|
setProjectFontSans(project.id, fontToSet === DEFAULT_FONT_VALUE ? null : fontToSet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUseGlobalFontMono = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
// Clear project font to use global
|
||||||
|
setProjectFontMono(project.id, null);
|
||||||
|
setFontMonoLocal(DEFAULT_FONT_VALUE);
|
||||||
|
} else {
|
||||||
|
// Set to current global font or default
|
||||||
|
const fontToSet = globalFontMono || DEFAULT_FONT_VALUE;
|
||||||
|
setFontMonoLocal(fontToSet);
|
||||||
|
setProjectFontMono(project.id, fontToSet === DEFAULT_FONT_VALUE ? null : fontToSet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleFontSansChange = (value: string) => {
|
const handleFontSansChange = (value: string) => {
|
||||||
setFontSansLocal(value);
|
setFontSansLocal(value);
|
||||||
// 'default' means use theme default, so we pass null to clear the override
|
|
||||||
setProjectFontSans(project.id, value === DEFAULT_FONT_VALUE ? null : value);
|
setProjectFontSans(project.id, value === DEFAULT_FONT_VALUE ? null : value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFontMonoChange = (value: string) => {
|
const handleFontMonoChange = (value: string) => {
|
||||||
setFontMonoLocal(value);
|
setFontMonoLocal(value);
|
||||||
// 'default' means use theme default, so we pass null to clear the override
|
|
||||||
setProjectFontMono(project.id, value === DEFAULT_FONT_VALUE ? null : value);
|
setProjectFontMono(project.id, value === DEFAULT_FONT_VALUE ? null : 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -90,10 +136,10 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
|||||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||||
<Palette className="w-5 h-5 text-brand-500" />
|
<Palette className="w-5 h-5 text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Theme</h2>
|
<h2 className="text-lg font-semibold text-foreground tracking-tight">Theme & Fonts</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||||
Customize the theme for this project.
|
Customize the appearance for this project.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
@@ -206,67 +252,112 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
|||||||
<Type className="w-4 h-4 text-muted-foreground" />
|
<Type className="w-4 h-4 text-muted-foreground" />
|
||||||
<Label className="text-foreground font-medium">Fonts</Label>
|
<Label className="text-foreground font-medium">Fonts</Label>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground -mt-2 mb-4">
|
|
||||||
Override the default fonts for this project. Fonts must be installed on your system.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="space-y-4">
|
||||||
{/* UI Font Selector */}
|
{/* UI Font */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label htmlFor="ui-font-select" className="text-sm">
|
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||||
UI Font
|
<Checkbox
|
||||||
</Label>
|
id="use-global-font-sans"
|
||||||
<Select value={fontSans} onValueChange={handleFontSansChange}>
|
checked={!hasCustomFontSans}
|
||||||
<SelectTrigger id="ui-font-select" className="w-full">
|
onCheckedChange={handleUseGlobalFontSans}
|
||||||
<SelectValue placeholder="Default (Geist Sans)" />
|
className="mt-1"
|
||||||
</SelectTrigger>
|
/>
|
||||||
<SelectContent>
|
<div className="flex-1 space-y-1.5">
|
||||||
{UI_SANS_FONT_OPTIONS.map((option) => (
|
<Label
|
||||||
<SelectItem key={option.value} value={option.value}>
|
htmlFor="use-global-font-sans"
|
||||||
<span
|
className="text-foreground cursor-pointer font-medium"
|
||||||
style={{
|
>
|
||||||
fontFamily:
|
Use Global UI Font
|
||||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
</Label>
|
||||||
}}
|
{!hasCustomFontSans && (
|
||||||
>
|
<p className="text-xs text-muted-foreground">
|
||||||
{option.label}
|
Currently using:{' '}
|
||||||
</span>
|
<span className="font-medium">{getGlobalFontSansLabel()}</span>
|
||||||
</SelectItem>
|
</p>
|
||||||
))}
|
)}
|
||||||
</SelectContent>
|
</div>
|
||||||
</Select>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Used for headings, labels, and UI text
|
{hasCustomFontSans && (
|
||||||
</p>
|
<div className="ml-6 space-y-2">
|
||||||
|
<Label htmlFor="ui-font-select" className="text-sm">
|
||||||
|
Project UI Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontSansLocal} onValueChange={handleFontSansChange}>
|
||||||
|
<SelectTrigger id="ui-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Sans)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_SANS_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Code Font Selector */}
|
{/* Code Font */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label htmlFor="code-font-select" className="text-sm">
|
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||||
Code Font
|
<Checkbox
|
||||||
</Label>
|
id="use-global-font-mono"
|
||||||
<Select value={fontMono} onValueChange={handleFontMonoChange}>
|
checked={!hasCustomFontMono}
|
||||||
<SelectTrigger id="code-font-select" className="w-full">
|
onCheckedChange={handleUseGlobalFontMono}
|
||||||
<SelectValue placeholder="Default (Geist Mono)" />
|
className="mt-1"
|
||||||
</SelectTrigger>
|
/>
|
||||||
<SelectContent>
|
<div className="flex-1 space-y-1.5">
|
||||||
{UI_MONO_FONT_OPTIONS.map((option) => (
|
<Label
|
||||||
<SelectItem key={option.value} value={option.value}>
|
htmlFor="use-global-font-mono"
|
||||||
<span
|
className="text-foreground cursor-pointer font-medium"
|
||||||
style={{
|
>
|
||||||
fontFamily:
|
Use Global Code Font
|
||||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
</Label>
|
||||||
}}
|
{!hasCustomFontMono && (
|
||||||
>
|
<p className="text-xs text-muted-foreground">
|
||||||
{option.label}
|
Currently using:{' '}
|
||||||
</span>
|
<span className="font-medium">{getGlobalFontMonoLabel()}</span>
|
||||||
</SelectItem>
|
</p>
|
||||||
))}
|
)}
|
||||||
</SelectContent>
|
</div>
|
||||||
</Select>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Used for code blocks and monospaced text
|
{hasCustomFontMono && (
|
||||||
</p>
|
<div className="ml-6 space-y-2">
|
||||||
|
<Label htmlFor="code-font-select" className="text-sm">
|
||||||
|
Project Code Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontMonoLocal} onValueChange={handleFontMonoChange}>
|
||||||
|
<SelectTrigger id="code-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Mono)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_MONO_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Palette, Moon, Sun } from 'lucide-react';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Palette, Moon, Sun, Type } from 'lucide-react';
|
||||||
import { darkThemes, lightThemes } from '@/config/theme-options';
|
import { darkThemes, lightThemes } 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 { cn } from '@/lib/utils';
|
||||||
|
import { useAppStore } from '@/store/app-store';
|
||||||
import type { Theme } from '../shared/types';
|
import type { Theme } from '../shared/types';
|
||||||
|
|
||||||
interface AppearanceSectionProps {
|
interface AppearanceSectionProps {
|
||||||
@@ -12,9 +25,22 @@ interface AppearanceSectionProps {
|
|||||||
|
|
||||||
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
|
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
|
||||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
||||||
|
const { fontFamilySans, fontFamilyMono, setFontSans, setFontMono } = useAppStore();
|
||||||
|
|
||||||
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
||||||
|
|
||||||
|
// Convert null to 'default' for Select component
|
||||||
|
const fontSansValue = fontFamilySans || DEFAULT_FONT_VALUE;
|
||||||
|
const fontMonoValue = fontFamilyMono || DEFAULT_FONT_VALUE;
|
||||||
|
|
||||||
|
const handleFontSansChange = (value: string) => {
|
||||||
|
setFontSans(value === DEFAULT_FONT_VALUE ? null : value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFontMonoChange = (value: string) => {
|
||||||
|
setFontMono(value === DEFAULT_FONT_VALUE ? null : value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -102,6 +128,77 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Fonts Section */}
|
||||||
|
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Type className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<Label className="text-foreground font-medium">Fonts</Label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground -mt-2 mb-4">
|
||||||
|
Set default fonts for all projects. Individual projects can override these settings.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* UI Font Selector */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="global-ui-font-select" className="text-sm">
|
||||||
|
UI Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontSansValue} onValueChange={handleFontSansChange}>
|
||||||
|
<SelectTrigger id="global-ui-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Sans)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_SANS_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Used for headings, labels, and UI text
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Font Selector */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="global-code-font-select" className="text-sm">
|
||||||
|
Code Font
|
||||||
|
</Label>
|
||||||
|
<Select value={fontMonoValue} onValueChange={handleFontMonoChange}>
|
||||||
|
<SelectTrigger id="global-code-font-select" className="w-full">
|
||||||
|
<SelectValue placeholder="Default (Geist Mono)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{UI_MONO_FONT_OPTIONS.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily:
|
||||||
|
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Used for code blocks and monospaced text
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -556,6 +556,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
|
|
||||||
useAppStore.setState({
|
useAppStore.setState({
|
||||||
theme: settings.theme as unknown as import('@/store/app-store').ThemeMode,
|
theme: settings.theme as unknown as import('@/store/app-store').ThemeMode,
|
||||||
|
fontFamilySans: settings.fontFamilySans ?? null,
|
||||||
|
fontFamilyMono: settings.fontFamilyMono ?? null,
|
||||||
sidebarOpen: settings.sidebarOpen ?? true,
|
sidebarOpen: settings.sidebarOpen ?? true,
|
||||||
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
||||||
maxConcurrency: settings.maxConcurrency ?? 3,
|
maxConcurrency: settings.maxConcurrency ?? 3,
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const SYNC_DEBOUNCE_MS = 1000;
|
|||||||
// Fields to sync to server (subset of AppState that should be persisted)
|
// Fields to sync to server (subset of AppState that should be persisted)
|
||||||
const SETTINGS_FIELDS_TO_SYNC = [
|
const SETTINGS_FIELDS_TO_SYNC = [
|
||||||
'theme',
|
'theme',
|
||||||
|
'fontFamilySans',
|
||||||
|
'fontFamilyMono',
|
||||||
'sidebarOpen',
|
'sidebarOpen',
|
||||||
'chatHistoryOpen',
|
'chatHistoryOpen',
|
||||||
'maxConcurrency',
|
'maxConcurrency',
|
||||||
|
|||||||
@@ -510,6 +510,10 @@ export interface AppState {
|
|||||||
// Theme
|
// Theme
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
|
|
||||||
|
// Fonts (global defaults)
|
||||||
|
fontFamilySans: string | null; // null = use default Geist Sans
|
||||||
|
fontFamilyMono: string | null; // null = use default Geist Mono
|
||||||
|
|
||||||
// Features/Kanban
|
// Features/Kanban
|
||||||
features: Feature[];
|
features: Feature[];
|
||||||
|
|
||||||
@@ -920,11 +924,13 @@ export interface AppActions {
|
|||||||
getEffectiveTheme: () => ThemeMode; // Get the effective theme (project, global, or preview if set)
|
getEffectiveTheme: () => ThemeMode; // Get the effective theme (project, global, or preview if set)
|
||||||
setPreviewTheme: (theme: ThemeMode | null) => void; // Set preview theme for hover preview (null to clear)
|
setPreviewTheme: (theme: ThemeMode | null) => void; // Set preview theme for hover preview (null to clear)
|
||||||
|
|
||||||
// Font actions (per-project)
|
// Font actions (global + per-project override)
|
||||||
setProjectFontSans: (projectId: string, fontFamily: string | null) => void; // Set per-project UI/sans font (null to clear)
|
setFontSans: (fontFamily: string | null) => void; // Set global UI/sans font (null to clear)
|
||||||
setProjectFontMono: (projectId: string, fontFamily: string | null) => void; // Set per-project code/mono font (null to clear)
|
setFontMono: (fontFamily: string | null) => void; // Set global code/mono font (null to clear)
|
||||||
getEffectiveFontSans: () => string | null; // Get effective UI font (project override or null for default)
|
setProjectFontSans: (projectId: string, fontFamily: string | null) => void; // Set per-project UI/sans font override (null = use global)
|
||||||
getEffectiveFontMono: () => string | null; // Get effective code font (project override or null for default)
|
setProjectFontMono: (projectId: string, fontFamily: string | null) => void; // Set per-project code/mono font override (null = use global)
|
||||||
|
getEffectiveFontSans: () => string | null; // Get effective UI font (project override -> global -> null for default)
|
||||||
|
getEffectiveFontMono: () => string | null; // Get effective code font (project override -> global -> null for default)
|
||||||
|
|
||||||
// Feature actions
|
// Feature actions
|
||||||
setFeatures: (features: Feature[]) => void;
|
setFeatures: (features: Feature[]) => void;
|
||||||
@@ -1264,6 +1270,8 @@ const initialState: AppState = {
|
|||||||
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
||||||
lastSelectedSessionByProject: {},
|
lastSelectedSessionByProject: {},
|
||||||
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
||||||
|
fontFamilySans: null, // Global UI font (null = use default Geist Sans)
|
||||||
|
fontFamilyMono: null, // Global code font (null = use default Geist Mono)
|
||||||
features: [],
|
features: [],
|
||||||
appSpec: '',
|
appSpec: '',
|
||||||
ipcConnected: false,
|
ipcConnected: false,
|
||||||
@@ -1739,7 +1747,11 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
|
|
||||||
setPreviewTheme: (theme) => set({ previewTheme: theme }),
|
setPreviewTheme: (theme) => set({ previewTheme: theme }),
|
||||||
|
|
||||||
// Font actions (per-project)
|
// Font actions (global + per-project override)
|
||||||
|
setFontSans: (fontFamily) => set({ fontFamilySans: fontFamily }),
|
||||||
|
|
||||||
|
setFontMono: (fontFamily) => set({ fontFamilyMono: fontFamily }),
|
||||||
|
|
||||||
setProjectFontSans: (projectId, fontFamily) => {
|
setProjectFontSans: (projectId, fontFamily) => {
|
||||||
// Update the project's fontFamilySans property
|
// Update the project's fontFamilySans property
|
||||||
const projects = get().projects.map((p) =>
|
const projects = get().projects.map((p) =>
|
||||||
@@ -1784,20 +1796,20 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
|
|
||||||
getEffectiveFontSans: () => {
|
getEffectiveFontSans: () => {
|
||||||
const currentProject = get().currentProject;
|
const currentProject = get().currentProject;
|
||||||
// Return the project's font override, or null for default
|
// Return project override if set, otherwise global, otherwise null for default
|
||||||
if (currentProject?.fontFamilySans) {
|
if (currentProject?.fontFamilySans) {
|
||||||
return currentProject.fontFamilySans;
|
return currentProject.fontFamilySans;
|
||||||
}
|
}
|
||||||
return null;
|
return get().fontFamilySans;
|
||||||
},
|
},
|
||||||
|
|
||||||
getEffectiveFontMono: () => {
|
getEffectiveFontMono: () => {
|
||||||
const currentProject = get().currentProject;
|
const currentProject = get().currentProject;
|
||||||
// Return the project's font override, or null for default
|
// Return project override if set, otherwise global, otherwise null for default
|
||||||
if (currentProject?.fontFamilyMono) {
|
if (currentProject?.fontFamilyMono) {
|
||||||
return currentProject.fontFamilyMono;
|
return currentProject.fontFamilyMono;
|
||||||
}
|
}
|
||||||
return null;
|
return get().fontFamilyMono;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Feature actions
|
// Feature actions
|
||||||
|
|||||||
@@ -463,6 +463,12 @@ export interface GlobalSettings {
|
|||||||
/** Currently selected theme */
|
/** Currently selected theme */
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
|
|
||||||
|
// Font Configuration
|
||||||
|
/** Global UI/Sans font family (undefined = use default Geist Sans) */
|
||||||
|
fontFamilySans?: string;
|
||||||
|
/** Global Code/Mono font family (undefined = use default Geist Mono) */
|
||||||
|
fontFamilyMono?: string;
|
||||||
|
|
||||||
// UI State Preferences
|
// UI State Preferences
|
||||||
/** Whether sidebar is currently open */
|
/** Whether sidebar is currently open */
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user