Merge branch 'auto-claude/056-add-design-system-themes-to-electron-app' into v2.6.0

This commit is contained in:
AndyMik90
2025-12-20 11:58:43 +01:00
13 changed files with 1143 additions and 109 deletions
+29 -31
View File
@@ -1,12 +1,12 @@
{
"name": "auto-claude-ui",
"version": "2.3.0",
"version": "2.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "auto-claude-ui",
"version": "2.3.0",
"version": "2.5.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@@ -150,7 +150,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -536,7 +535,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -560,7 +558,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -600,7 +597,6 @@
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
@@ -995,6 +991,7 @@
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"dependencies": {
"cross-dirname": "^0.1.0",
"debug": "^4.3.4",
@@ -1016,6 +1013,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -3930,7 +3928,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -4127,7 +4126,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -4138,7 +4136,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -4230,7 +4227,6 @@
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
@@ -4630,8 +4626,7 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/7zip-bin": {
"version": "5.2.0",
@@ -4653,7 +4648,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4714,7 +4708,6 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -4887,6 +4880,7 @@
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
@@ -5271,7 +5265,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -5951,7 +5944,8 @@
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
"dev": true,
"license": "MIT",
"optional": true
"optional": true,
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.6",
@@ -6295,7 +6289,6 @@
"integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "26.0.12",
"builder-util": "26.0.11",
@@ -6353,7 +6346,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/dotenv": {
"version": "16.6.1",
@@ -6429,7 +6423,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^22.7.7",
@@ -6558,6 +6551,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@electron/asar": "^3.2.1",
"debug": "^4.1.1",
@@ -6578,6 +6572,7 @@
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
@@ -6593,6 +6588,7 @@
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"license": "MIT",
"peer": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -6603,6 +6599,7 @@
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 4.0.0"
}
@@ -6972,7 +6969,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8992,7 +8988,6 @@
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
@@ -9936,6 +9931,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -11775,7 +11771,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -11873,7 +11868,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -11910,6 +11904,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"commander": "^9.4.0"
},
@@ -11927,6 +11922,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": "^12.20.0 || >=14"
}
@@ -11947,6 +11943,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -11962,6 +11959,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -11974,7 +11972,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/proc-log": {
"version": "2.0.1",
@@ -12078,7 +12077,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12088,7 +12086,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -13405,8 +13402,7 @@
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@@ -13463,6 +13459,7 @@
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "~2.6.2"
@@ -13489,6 +13486,7 @@
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -13510,6 +13508,7 @@
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -13523,6 +13522,7 @@
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"minimist": "^1.2.6"
},
@@ -13537,6 +13537,7 @@
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -13853,7 +13854,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14194,7 +14194,6 @@
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -15228,7 +15227,6 @@
"integrity": "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+24 -6
View File
@@ -43,7 +43,8 @@ import { useTaskStore, loadTasks } from './stores/task-store';
import { useSettingsStore, loadSettings } from './stores/settings-store';
import { useTerminalStore, restoreTerminalSessions } from './stores/terminal-store';
import { useIpcListeners } from './hooks/useIpc';
import type { Task, Project } from '../shared/types';
import { COLOR_THEMES } from '../shared/constants';
import type { Task, Project, ColorTheme } from '../shared/types';
export function App() {
// Load IPC listeners for real-time updates
@@ -177,21 +178,38 @@ export function App() {
// Apply theme on load
useEffect(() => {
const root = document.documentElement;
const applyTheme = () => {
// Apply light/dark mode
if (settings.theme === 'dark') {
document.documentElement.classList.add('dark');
root.classList.add('dark');
} else if (settings.theme === 'light') {
document.documentElement.classList.remove('dark');
root.classList.remove('dark');
} else {
// System preference
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
root.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
root.classList.remove('dark');
}
}
};
// Apply color theme via data-theme attribute
// Validate colorTheme against known themes, fallback to 'default' if invalid
const validThemeIds = COLOR_THEMES.map((t) => t.id);
const rawColorTheme = settings.colorTheme ?? 'default';
const colorTheme: ColorTheme = validThemeIds.includes(rawColorTheme as ColorTheme)
? (rawColorTheme as ColorTheme)
: 'default';
if (colorTheme === 'default') {
root.removeAttribute('data-theme');
} else {
root.setAttribute('data-theme', colorTheme);
}
applyTheme();
// Listen for system theme changes
@@ -206,7 +224,7 @@ export function App() {
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, [settings.theme]);
}, [settings.theme, settings.colorTheme]);
// Update selected task when tasks change (for real-time updates)
useEffect(() => {
@@ -4,8 +4,6 @@ import {
Plus,
Settings,
Trash2,
Moon,
Sun,
LayoutGrid,
Terminal,
Map,
@@ -53,7 +51,7 @@ import {
checkProjectVersion,
updateProjectAutoBuild
} from '../stores/project-store';
import { useSettingsStore, saveSettings } from '../stores/settings-store';
import { useSettingsStore } from '../stores/settings-store';
import { AddProjectModal } from './AddProjectModal';
import { GitSetupModal } from './GitSetupModal';
import { RateLimitIndicator } from './RateLimitIndicator';
@@ -267,21 +265,6 @@ export function Sidebar({
}
};
const toggleTheme = () => {
const newTheme = settings.theme === 'dark' ? 'light' : 'dark';
saveSettings({ theme: newTheme });
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
const isDark =
settings.theme === 'dark' ||
(settings.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
const handleNavClick = (view: SidebarView) => {
onViewChange?.(view);
};
@@ -317,18 +300,8 @@ export function Sidebar({
<TooltipProvider>
<div className="flex h-full w-64 flex-col bg-sidebar border-r border-border">
{/* Header with drag area - extra top padding for macOS traffic lights */}
<div className="electron-drag flex h-14 items-center justify-between px-4 pt-6">
<div className="electron-drag flex h-14 items-center px-4 pt-6">
<span className="electron-no-drag text-lg font-bold text-primary">Auto Claude</span>
<div className="electron-no-drag flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" onClick={toggleTheme}>
{isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</Button>
</TooltipTrigger>
<TooltipContent>Toggle theme</TooltipContent>
</Tooltip>
</div>
</div>
<Separator className="mt-2" />
@@ -77,7 +77,7 @@ const projectNavItems: NavItem<ProjectSettingsSection>[] = [
* Coordinates app and project settings sections
*/
export function AppSettingsDialog({ open, onOpenChange, initialSection, initialProjectSection, onRerunWizard }: AppSettingsDialogProps) {
const { settings, setSettings, isSaving, error, saveSettings } = useSettings();
const { settings, setSettings, isSaving, error, saveSettings, revertTheme, commitTheme } = useSettings();
const [version, setVersion] = useState<string>('');
// Track which top-level section is active
@@ -138,10 +138,17 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
}
if (appSaveSuccess) {
// Commit the theme so future cancels won't revert to old values
commitTheme();
onOpenChange(false);
}
};
const handleCancel = () => {
// onOpenChange handler will revert theme changes
onOpenChange(false);
};
const handleProjectChange = (projectId: string | null) => {
selectProject(projectId);
};
@@ -183,7 +190,14 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
const projectNavDisabled = !selectedProjectId;
return (
<FullScreenDialog open={open} onOpenChange={onOpenChange}>
<FullScreenDialog open={open} onOpenChange={(newOpen) => {
if (!newOpen) {
// Dialog is being closed (via X, escape, or overlay click)
// Revert any unsaved theme changes
revertTheme();
}
onOpenChange(newOpen);
}}>
<FullScreenDialogContent>
<FullScreenDialogHeader>
<FullScreenDialogTitle className="flex items-center gap-3">
@@ -332,7 +346,7 @@ export function AppSettingsDialog({ open, onOpenChange, initialSection, initialP
{error || projectError}
</div>
)}
<Button variant="outline" onClick={() => onOpenChange(false)}>
<Button variant="outline" onClick={handleCancel}>
Cancel
</Button>
<Button
@@ -0,0 +1,138 @@
import { Check, Sun, Moon, Monitor } from 'lucide-react';
import { cn } from '../../lib/utils';
import { Label } from '../ui/label';
import { COLOR_THEMES } from '../../../shared/constants';
import { useSettingsStore } from '../../stores/settings-store';
import type { ColorTheme, AppSettings } from '../../../shared/types';
interface ThemeSelectorProps {
settings: AppSettings;
onSettingsChange: (settings: AppSettings) => void;
}
/**
* Theme selector component displaying a grid of theme cards with preview swatches
* and a 3-option mode toggle (Light/Dark/System)
*
* Theme changes are applied immediately for live preview, while other settings
* require saving to take effect.
*/
export function ThemeSelector({ settings, onSettingsChange }: ThemeSelectorProps) {
const updateStoreSettings = useSettingsStore((state) => state.updateSettings);
const currentColorTheme = settings.colorTheme || 'default';
const currentMode = settings.theme;
const isDark = currentMode === 'dark' ||
(currentMode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
const handleColorThemeChange = (themeId: ColorTheme) => {
// Update local draft state
onSettingsChange({ ...settings, colorTheme: themeId });
// Apply immediately to store for live preview (triggers App.tsx useEffect)
updateStoreSettings({ colorTheme: themeId });
};
const handleModeChange = (mode: 'light' | 'dark' | 'system') => {
// Update local draft state
onSettingsChange({ ...settings, theme: mode });
// Apply immediately to store for live preview (triggers App.tsx useEffect)
updateStoreSettings({ theme: mode });
};
const getModeIcon = (mode: string) => {
switch (mode) {
case 'light':
return <Sun className="h-4 w-4" />;
case 'dark':
return <Moon className="h-4 w-4" />;
default:
return <Monitor className="h-4 w-4" />;
}
};
return (
<div className="space-y-6">
{/* Mode Toggle */}
<div className="space-y-3">
<Label className="text-sm font-medium text-foreground">Appearance Mode</Label>
<p className="text-sm text-muted-foreground">Choose light, dark, or system preference</p>
<div className="grid grid-cols-3 gap-3 max-w-md pt-1">
{(['system', 'light', 'dark'] as const).map((mode) => (
<button
key={mode}
onClick={() => handleModeChange(mode)}
className={cn(
'flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-all',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
currentMode === mode
? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50 hover:bg-accent/50'
)}
>
{getModeIcon(mode)}
<span className="text-sm font-medium capitalize">{mode}</span>
</button>
))}
</div>
</div>
{/* Color Theme Grid */}
<div className="space-y-3">
<Label className="text-sm font-medium text-foreground">Color Theme</Label>
<p className="text-sm text-muted-foreground">Select a color palette for the interface</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 pt-1">
{COLOR_THEMES.map((theme) => {
const isSelected = currentColorTheme === theme.id;
const bgColor = isDark ? theme.previewColors.darkBg : theme.previewColors.bg;
const accentColor = isDark
? (theme.previewColors.darkAccent || theme.previewColors.accent)
: theme.previewColors.accent;
return (
<button
key={theme.id}
onClick={() => handleColorThemeChange(theme.id)}
className={cn(
'relative flex flex-col p-4 rounded-lg border-2 text-left transition-all',
'hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isSelected
? 'border-primary bg-primary/5 shadow-sm'
: 'border-border hover:border-primary/50 hover:bg-accent/30'
)}
>
{/* Selection indicator */}
{isSelected && (
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-primary flex items-center justify-center">
<Check className="w-3 h-3 text-primary-foreground" />
</div>
)}
{/* Preview swatches */}
<div className="flex items-center gap-2 mb-3">
<div className="flex -space-x-1.5">
<div
className="w-6 h-6 rounded-full border-2 border-background shadow-sm"
style={{ backgroundColor: bgColor }}
title="Background color"
/>
<div
className="w-6 h-6 rounded-full border-2 border-background shadow-sm"
style={{ backgroundColor: accentColor }}
title="Accent color"
/>
</div>
</div>
{/* Theme info */}
<div className="space-y-1">
<p className="font-medium text-sm text-foreground">{theme.name}</p>
<p className="text-xs text-muted-foreground line-clamp-2">{theme.description}</p>
</div>
</button>
);
})}
</div>
</div>
</div>
);
}
@@ -1,7 +1,5 @@
import { Sun, Moon, Monitor } from 'lucide-react';
import { Label } from '../ui/label';
import { cn } from '../../lib/utils';
import { SettingsSection } from './SettingsSection';
import { ThemeSelector } from './ThemeSelector';
import type { AppSettings } from '../../../shared/types';
interface ThemeSettingsProps {
@@ -11,47 +9,15 @@ interface ThemeSettingsProps {
/**
* Theme and appearance settings section
* Wraps the ThemeSelector component with a consistent settings section layout
*/
export function ThemeSettings({ settings, onSettingsChange }: ThemeSettingsProps) {
const getThemeIcon = (theme: string) => {
switch (theme) {
case 'light':
return <Sun className="h-4 w-4" />;
case 'dark':
return <Moon className="h-4 w-4" />;
default:
return <Monitor className="h-4 w-4" />;
}
};
return (
<SettingsSection
title="Appearance"
description="Customize how Auto Claude looks"
>
<div className="space-y-4">
<div className="space-y-3">
<Label htmlFor="theme" className="text-sm font-medium text-foreground">Theme</Label>
<p className="text-sm text-muted-foreground">Choose your preferred color scheme</p>
<div className="grid grid-cols-3 gap-3">
{(['system', 'light', 'dark'] as const).map((theme) => (
<button
key={theme}
onClick={() => onSettingsChange({ ...settings, theme })}
className={cn(
'flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-all',
settings.theme === theme
? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50 hover:bg-accent/50'
)}
>
{getThemeIcon(theme)}
<span className="text-sm font-medium capitalize">{theme}</span>
</button>
))}
</div>
</div>
</div>
<ThemeSelector settings={settings} onSettingsChange={onSettingsChange} />
</SettingsSection>
);
}
@@ -1,25 +1,41 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef, useCallback } from 'react';
import { useSettingsStore, saveSettings as saveSettingsToStore, loadSettings as loadSettingsFromStore } from '../../../stores/settings-store';
import type { AppSettings } from '../../../../shared/types';
/**
* Custom hook for managing application settings
* Provides state management and save/load functionality
*
* Theme changes are applied immediately for live preview. If the user cancels
* without saving, call revertTheme() to restore the original theme.
*/
export function useSettings() {
const currentSettings = useSettingsStore((state) => state.settings);
const updateStoreSettings = useSettingsStore((state) => state.updateSettings);
const [settings, setSettings] = useState<AppSettings>(currentSettings);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
// Store the original theme settings when the hook mounts (dialog opens)
// This allows us to revert if the user cancels
const originalThemeRef = useRef<{ theme: AppSettings['theme']; colorTheme: AppSettings['colorTheme'] }>({
theme: currentSettings.theme,
colorTheme: currentSettings.colorTheme
});
// Sync with store
useEffect(() => {
setSettings(currentSettings);
}, [currentSettings]);
// Load settings on mount
// Load settings on mount and capture original theme
useEffect(() => {
loadSettingsFromStore();
// Update the original theme ref when settings load
originalThemeRef.current = {
theme: currentSettings.theme,
colorTheme: currentSettings.colorTheme
};
}, []);
const saveSettings = async () => {
@@ -63,6 +79,29 @@ export function useSettings() {
setSettings((prev) => ({ ...prev, ...partial }));
};
/**
* Revert theme to the original values (before any preview changes).
* Call this when the user cancels the settings dialog without saving.
*/
const revertTheme = useCallback(() => {
const original = originalThemeRef.current;
updateStoreSettings({
theme: original.theme,
colorTheme: original.colorTheme
});
}, [updateStoreSettings]);
/**
* Capture the current theme as the new "original" after successful save.
* This updates the reference point for future reverts.
*/
const commitTheme = useCallback(() => {
originalThemeRef.current = {
theme: settings.theme,
colorTheme: settings.colorTheme
};
}, [settings.theme, settings.colorTheme]);
return {
settings,
setSettings,
@@ -70,6 +109,8 @@ export function useSettings() {
isSaving,
error,
saveSettings,
applyTheme
applyTheme,
revertTheme,
commitTheme
};
}
@@ -5,6 +5,7 @@
export { AppSettingsDialog, type AppSection } from './AppSettings';
export { ThemeSettings } from './ThemeSettings';
export { ThemeSelector } from './ThemeSelector';
export { GeneralSettings } from './GeneralSettings';
export { IntegrationSettings } from './IntegrationSettings';
export { AdvancedSettings } from './AdvancedSettings';
@@ -204,6 +204,810 @@
--shadow-focus: 0 0 0 2px rgba(214, 216, 118, 0.2);
}
/* ============================================
DUSK THEME (Light)
Warm, muted palette inspired by Fey/Oscura
============================================ */
[data-theme="dusk"] {
/* Backgrounds */
--background: #F5F5F0;
--foreground: #131419;
/* Card surfaces */
--card: #FFFFFF;
--card-foreground: #131419;
/* Primary accent - muted olive/yellow */
--primary: #B8B978;
--primary-foreground: #131419;
/* Secondary */
--secondary: #EAEAE5;
--secondary-foreground: #131419;
/* Muted */
--muted: #F0F0EB;
--muted-foreground: #5C6974;
/* Accent */
--accent: #F0F0E0;
--accent-foreground: #B8B978;
/* Destructive */
--destructive: #D84F68;
--destructive-foreground: #FFFFFF;
/* Borders and inputs */
--border: #E0E0DB;
--input: #E0E0DB;
--ring: #B8B978;
/* Sidebar */
--sidebar: #FFFFFF;
--sidebar-foreground: #131419;
/* Popover */
--popover: #FFFFFF;
--popover-foreground: #131419;
/* Semantic colors */
--success: #4EBE96;
--success-foreground: #FFFFFF;
--success-light: #E0F5ED;
--warning: #D2D714;
--warning-foreground: #131419;
--warning-light: #F5F5D0;
--info: #479FFA;
--info-foreground: #FFFFFF;
--info-light: #E8F4FF;
--error: #D84F68;
--error-light: #FCE8EC;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
--shadow-focus: 0 0 0 3px rgba(184, 185, 120, 0.2);
}
/* ============================================
DUSK THEME (Dark)
Fey-inspired dark theme
============================================ */
[data-theme="dusk"].dark {
/* Backgrounds */
--background: #131419;
--foreground: #E6E6E6;
/* Card surfaces */
--card: #1A1B21;
--card-foreground: #E6E6E6;
/* Primary accent - pale yellow */
--primary: #E6E7A3;
--primary-foreground: #131419;
/* Secondary */
--secondary: #222329;
--secondary-foreground: #E6E6E6;
/* Muted */
--muted: #16171D;
--muted-foreground: #868F97;
/* Accent */
--accent: #2A2B1F;
--accent-foreground: #E6E7A3;
/* Destructive */
--destructive: #D84F68;
--destructive-foreground: #131419;
/* Borders and inputs */
--border: #282828;
--input: #282828;
--ring: #E6E7A3;
/* Sidebar */
--sidebar: #16171D;
--sidebar-foreground: #E6E6E6;
/* Popover */
--popover: #222329;
--popover-foreground: #E6E6E6;
/* Semantic colors */
--success: #4EBE96;
--success-foreground: #131419;
--success-light: #1A2E28;
--warning: #D2D714;
--warning-foreground: #131419;
--warning-light: #2A2B1A;
--info: #479FFA;
--info-foreground: #131419;
--info-light: #1A2433;
--error: #D84F68;
--error-light: #2E1A1F;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8);
--shadow-focus: 0 0 0 2px rgba(230, 231, 163, 0.25);
}
/* ============================================
LIME THEME (Light)
Fresh, energetic lime/chartreuse theme
============================================ */
[data-theme="lime"] {
/* Backgrounds */
--background: #E8F5A3;
--foreground: #1A1A2E;
/* Card surfaces */
--card: #FFFFFF;
--card-foreground: #1A1A2E;
/* Primary accent - purple for contrast against lime */
--primary: #7C3AED;
--primary-foreground: #FFFFFF;
/* Secondary */
--secondary: #F5F9E8;
--secondary-foreground: #1A1A2E;
/* Muted */
--muted: #F8FAFC;
--muted-foreground: #64748B;
/* Accent */
--accent: #EDE9FE;
--accent-foreground: #7C3AED;
/* Destructive */
--destructive: #DC2626;
--destructive-foreground: #FFFFFF;
/* Borders and inputs */
--border: #E2E8F0;
--input: #E2E8F0;
--ring: #7C3AED;
/* Sidebar */
--sidebar: #FFFFFF;
--sidebar-foreground: #1A1A2E;
/* Popover */
--popover: #FFFFFF;
--popover-foreground: #1A1A2E;
/* Semantic colors */
--success: #059669;
--success-foreground: #FFFFFF;
--success-light: #D1FAE5;
--warning: #D97706;
--warning-foreground: #FFFFFF;
--warning-light: #FEF3C7;
--info: #2563EB;
--info-foreground: #FFFFFF;
--info-light: #DBEAFE;
--error: #DC2626;
--error-light: #FEE2E2;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
--shadow-focus: 0 0 0 3px rgba(124, 58, 237, 0.2);
}
/* ============================================
LIME THEME (Dark)
Fresh lime theme with deep purple undertones
============================================ */
[data-theme="lime"].dark {
/* Backgrounds */
--background: #0F0F1A;
--foreground: #F8FAFC;
/* Card surfaces */
--card: #1E1E2E;
--card-foreground: #F8FAFC;
/* Primary accent - bright purple for dark mode */
--primary: #8B5CF6;
--primary-foreground: #0F0F1A;
/* Secondary */
--secondary: #1A1A2E;
--secondary-foreground: #F8FAFC;
/* Muted */
--muted: #13131F;
--muted-foreground: #A1A1B5;
/* Accent */
--accent: #2E2350;
--accent-foreground: #8B5CF6;
/* Destructive */
--destructive: #F87171;
--destructive-foreground: #0F0F1A;
/* Borders and inputs */
--border: #2E2E40;
--input: #2E2E40;
--ring: #8B5CF6;
/* Sidebar */
--sidebar: #13131F;
--sidebar-foreground: #F8FAFC;
/* Popover */
--popover: #262638;
--popover-foreground: #F8FAFC;
/* Semantic colors */
--success: #34D399;
--success-foreground: #0F0F1A;
--success-light: #134E4A;
--warning: #FBBF24;
--warning-foreground: #0F0F1A;
--warning-light: #451A03;
--info: #60A5FA;
--info-foreground: #0F0F1A;
--info-light: #1E3A8A;
--error: #F87171;
--error-light: #450A0A;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8);
--shadow-focus: 0 0 0 3px rgba(139, 92, 246, 0.3);
}
/* ============================================
OCEAN THEME (Light)
Calm, professional blue tones
============================================ */
[data-theme="ocean"] {
/* Backgrounds */
--background: #E0F2FE;
--foreground: #0C4A6E;
/* Card surfaces */
--card: #FFFFFF;
--card-foreground: #0C4A6E;
/* Primary accent - sky blue */
--primary: #0284C7;
--primary-foreground: #FFFFFF;
/* Secondary */
--secondary: #F0F9FF;
--secondary-foreground: #0C4A6E;
/* Muted */
--muted: #F8FAFC;
--muted-foreground: #64748B;
/* Accent */
--accent: #E0F2FE;
--accent-foreground: #0284C7;
/* Destructive */
--destructive: #DC2626;
--destructive-foreground: #FFFFFF;
/* Borders and inputs */
--border: #BAE6FD;
--input: #BAE6FD;
--ring: #0284C7;
/* Sidebar */
--sidebar: #FFFFFF;
--sidebar-foreground: #0C4A6E;
/* Popover */
--popover: #FFFFFF;
--popover-foreground: #0C4A6E;
/* Semantic colors */
--success: #059669;
--success-foreground: #FFFFFF;
--success-light: #D1FAE5;
--warning: #D97706;
--warning-foreground: #FFFFFF;
--warning-light: #FEF3C7;
--info: #2563EB;
--info-foreground: #FFFFFF;
--info-light: #DBEAFE;
--error: #DC2626;
--error-light: #FEE2E2;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
--shadow-focus: 0 0 0 3px rgba(2, 132, 199, 0.2);
}
/* ============================================
OCEAN THEME (Dark)
Deep ocean blue tones
============================================ */
[data-theme="ocean"].dark {
/* Backgrounds */
--background: #082F49;
--foreground: #F0F9FF;
/* Card surfaces */
--card: #164E63;
--card-foreground: #F0F9FF;
/* Primary accent - bright sky blue */
--primary: #38BDF8;
--primary-foreground: #082F49;
/* Secondary */
--secondary: #0C4A6E;
--secondary-foreground: #F0F9FF;
/* Muted */
--muted: #0A3D5C;
--muted-foreground: #7DD3FC;
/* Accent */
--accent: #0C4A6E;
--accent-foreground: #38BDF8;
/* Destructive */
--destructive: #F87171;
--destructive-foreground: #082F49;
/* Borders and inputs */
--border: #0E7490;
--input: #0E7490;
--ring: #38BDF8;
/* Sidebar */
--sidebar: #0A3D5C;
--sidebar-foreground: #F0F9FF;
/* Popover */
--popover: #1E6B8A;
--popover-foreground: #F0F9FF;
/* Semantic colors */
--success: #34D399;
--success-foreground: #082F49;
--success-light: #134E4A;
--warning: #FBBF24;
--warning-foreground: #082F49;
--warning-light: #451A03;
--info: #60A5FA;
--info-foreground: #082F49;
--info-light: #1E3A8A;
--error: #F87171;
--error-light: #450A0A;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8);
--shadow-focus: 0 0 0 3px rgba(56, 189, 248, 0.3);
}
/* ============================================
RETRO THEME (Light)
Warm, nostalgic amber/orange vibes
============================================ */
[data-theme="retro"] {
/* Backgrounds */
--background: #FEF3C7;
--foreground: #78350F;
/* Card surfaces */
--card: #FFFFFF;
--card-foreground: #78350F;
/* Primary accent - warm amber/orange */
--primary: #D97706;
--primary-foreground: #FFFFFF;
/* Secondary */
--secondary: #FFFBEB;
--secondary-foreground: #78350F;
/* Muted */
--muted: #FEFCE8;
--muted-foreground: #92400E;
/* Accent */
--accent: #FEF3C7;
--accent-foreground: #D97706;
/* Destructive */
--destructive: #B91C1C;
--destructive-foreground: #FFFFFF;
/* Borders and inputs */
--border: #FDE68A;
--input: #FDE68A;
--ring: #D97706;
/* Sidebar */
--sidebar: #FFFFFF;
--sidebar-foreground: #78350F;
/* Popover */
--popover: #FFFFFF;
--popover-foreground: #78350F;
/* Semantic colors */
--success: #15803D;
--success-foreground: #FFFFFF;
--success-light: #DCFCE7;
--warning: #CA8A04;
--warning-foreground: #78350F;
--warning-light: #FEF9C3;
--info: #1D4ED8;
--info-foreground: #FFFFFF;
--info-light: #DBEAFE;
--error: #B91C1C;
--error-light: #FEE2E2;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
--shadow-focus: 0 0 0 3px rgba(217, 119, 6, 0.2);
}
/* ============================================
RETRO THEME (Dark)
Warm stone/brown tones with golden accents
============================================ */
[data-theme="retro"].dark {
/* Backgrounds */
--background: #1C1917;
--foreground: #FEFCE8;
/* Card surfaces */
--card: #44403C;
--card-foreground: #FEFCE8;
/* Primary accent - bright amber/gold */
--primary: #FBBF24;
--primary-foreground: #1C1917;
/* Secondary */
--secondary: #292524;
--secondary-foreground: #FEFCE8;
/* Muted */
--muted: #1C1917;
--muted-foreground: #FDE68A;
/* Accent */
--accent: #451A03;
--accent-foreground: #FBBF24;
/* Destructive */
--destructive: #F87171;
--destructive-foreground: #1C1917;
/* Borders and inputs */
--border: #78716C;
--input: #78716C;
--ring: #FBBF24;
/* Sidebar */
--sidebar: #1C1917;
--sidebar-foreground: #FEFCE8;
/* Popover */
--popover: #57534E;
--popover-foreground: #FEFCE8;
/* Semantic colors */
--success: #4ADE80;
--success-foreground: #1C1917;
--success-light: #14532D;
--warning: #FACC15;
--warning-foreground: #1C1917;
--warning-light: #422006;
--info: #60A5FA;
--info-foreground: #1C1917;
--info-light: #1E3A8A;
--error: #F87171;
--error-light: #450A0A;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8);
--shadow-focus: 0 0 0 3px rgba(251, 191, 36, 0.3);
}
/* ============================================
NEO THEME (Light)
Modern, cyberpunk-inspired pink/purple palette
============================================ */
[data-theme="neo"] {
/* Backgrounds */
--background: #FDF4FF;
--foreground: #581C87;
/* Card surfaces */
--card: #FFFFFF;
--card-foreground: #581C87;
/* Primary accent - fuchsia/magenta */
--primary: #D946EF;
--primary-foreground: #FFFFFF;
/* Secondary */
--secondary: #FAF5FF;
--secondary-foreground: #581C87;
/* Muted */
--muted: #F5F3FF;
--muted-foreground: #7C3AED;
/* Accent */
--accent: #FAE8FF;
--accent-foreground: #D946EF;
/* Destructive */
--destructive: #E11D48;
--destructive-foreground: #FFFFFF;
/* Borders and inputs */
--border: #F0ABFC;
--input: #F0ABFC;
--ring: #D946EF;
/* Sidebar */
--sidebar: #FFFFFF;
--sidebar-foreground: #581C87;
/* Popover */
--popover: #FFFFFF;
--popover-foreground: #581C87;
/* Semantic colors */
--success: #06B6D4;
--success-foreground: #FFFFFF;
--success-light: #CFFAFE;
--warning: #F59E0B;
--warning-foreground: #581C87;
--warning-light: #FEF3C7;
--info: #8B5CF6;
--info-foreground: #FFFFFF;
--info-light: #EDE9FE;
--error: #E11D48;
--error-light: #FFE4E6;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
--shadow-focus: 0 0 0 3px rgba(217, 70, 239, 0.2);
}
/* ============================================
NEO THEME (Dark)
Cyberpunk dark mode with neon pink/purple glow
============================================ */
[data-theme="neo"].dark {
/* Backgrounds */
--background: #0F0720;
--foreground: #FAF5FF;
/* Card surfaces */
--card: #2D1B4E;
--card-foreground: #FAF5FF;
/* Primary accent - bright pink/fuchsia */
--primary: #F0ABFC;
--primary-foreground: #0F0720;
/* Secondary */
--secondary: #1A0A30;
--secondary-foreground: #FAF5FF;
/* Muted */
--muted: #150825;
--muted-foreground: #E879F9;
/* Accent */
--accent: #581C87;
--accent-foreground: #F0ABFC;
/* Destructive */
--destructive: #FB7185;
--destructive-foreground: #0F0720;
/* Borders and inputs */
--border: #581C87;
--input: #581C87;
--ring: #F0ABFC;
/* Sidebar */
--sidebar: #150825;
--sidebar-foreground: #FAF5FF;
/* Popover */
--popover: #3D2563;
--popover-foreground: #FAF5FF;
/* Semantic colors */
--success: #22D3EE;
--success-foreground: #0F0720;
--success-light: #164E63;
--warning: #FBBF24;
--warning-foreground: #0F0720;
--warning-light: #451A03;
--info: #A78BFA;
--info-foreground: #0F0720;
--info-light: #4C1D95;
--error: #FB7185;
--error-light: #4C0519;
/* Shadows - with subtle neon glow effect */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4), 0 0 20px rgba(217, 70, 239, 0.1);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 0 30px rgba(217, 70, 239, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 0 40px rgba(217, 70, 239, 0.15);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6), 0 0 50px rgba(217, 70, 239, 0.2);
--shadow-focus: 0 0 0 3px rgba(240, 171, 252, 0.4);
}
/* ============================================
FOREST THEME (Light)
Natural, earthy green tones
============================================ */
[data-theme="forest"] {
/* Backgrounds */
--background: #DCFCE7;
--foreground: #14532D;
/* Card surfaces */
--card: #FFFFFF;
--card-foreground: #14532D;
/* Primary accent - natural green */
--primary: #16A34A;
--primary-foreground: #FFFFFF;
/* Secondary */
--secondary: #F0FDF4;
--secondary-foreground: #14532D;
/* Muted */
--muted: #ECFDF5;
--muted-foreground: #166534;
/* Accent */
--accent: #DCFCE7;
--accent-foreground: #16A34A;
/* Destructive */
--destructive: #DC2626;
--destructive-foreground: #FFFFFF;
/* Borders and inputs */
--border: #86EFAC;
--input: #86EFAC;
--ring: #16A34A;
/* Sidebar */
--sidebar: #FFFFFF;
--sidebar-foreground: #14532D;
/* Popover */
--popover: #FFFFFF;
--popover-foreground: #14532D;
/* Semantic colors */
--success: #059669;
--success-foreground: #FFFFFF;
--success-light: #D1FAE5;
--warning: #CA8A04;
--warning-foreground: #14532D;
--warning-light: #FEF9C3;
--info: #0284C7;
--info-foreground: #FFFFFF;
--info-light: #E0F2FE;
--error: #DC2626;
--error-light: #FEE2E2;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
--shadow-focus: 0 0 0 3px rgba(22, 163, 74, 0.2);
}
/* ============================================
FOREST THEME (Dark)
Deep forest green tones with bright green accents
============================================ */
[data-theme="forest"].dark {
/* Backgrounds */
--background: #052E16;
--foreground: #F0FDF4;
/* Card surfaces */
--card: #166534;
--card-foreground: #F0FDF4;
/* Primary accent - bright green for dark mode */
--primary: #4ADE80;
--primary-foreground: #052E16;
/* Secondary */
--secondary: #14532D;
--secondary-foreground: #F0FDF4;
/* Muted */
--muted: #0A3D1F;
--muted-foreground: #86EFAC;
/* Accent */
--accent: #14532D;
--accent-foreground: #4ADE80;
/* Destructive */
--destructive: #F87171;
--destructive-foreground: #052E16;
/* Borders and inputs */
--border: #166534;
--input: #166534;
--ring: #4ADE80;
/* Sidebar */
--sidebar: #0A3D1F;
--sidebar-foreground: #F0FDF4;
/* Popover */
--popover: #15803D;
--popover-foreground: #F0FDF4;
/* Semantic colors */
--success: #34D399;
--success-foreground: #052E16;
--success-light: #064E3B;
--warning: #FBBF24;
--warning-foreground: #052E16;
--warning-light: #451A03;
--info: #38BDF8;
--info-foreground: #052E16;
--info-light: #0C4A6E;
--error: #F87171;
--error-light: #450A0A;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8);
--shadow-focus: 0 0 0 3px rgba(74, 222, 128, 0.3);
}
/* Base styles */
* {
border-color: var(--border);
@@ -9,6 +9,7 @@
export const DEFAULT_APP_SETTINGS = {
theme: 'system' as const,
colorTheme: 'default' as const,
defaultModel: 'opus',
agentFramework: 'auto-claude',
pythonPath: undefined as string | undefined,
@@ -21,6 +21,9 @@ export * from './changelog';
// Model and agent profile constants
export * from './models';
// Theme constants
export * from './themes';
// GitHub integration constants
export * from './github';
@@ -0,0 +1,59 @@
/**
* Theme constants
* Color themes for multi-theme support with light/dark mode variants
*/
import type { ColorThemeDefinition } from '../types/settings';
// ============================================
// Color Themes
// ============================================
/**
* All available color themes with preview colors for the theme selector.
* Each theme has both light and dark mode variants defined in CSS.
*/
export const COLOR_THEMES: ColorThemeDefinition[] = [
{
id: 'default',
name: 'Default',
description: 'Oscura-inspired with pale yellow accent',
previewColors: { bg: '#F2F2ED', accent: '#E6E7A3', darkBg: '#0B0B0F', darkAccent: '#E6E7A3' }
},
{
id: 'dusk',
name: 'Dusk',
description: 'Warmer variant with slightly lighter dark mode',
previewColors: { bg: '#F5F5F0', accent: '#E6E7A3', darkBg: '#131419', darkAccent: '#E6E7A3' }
},
{
id: 'lime',
name: 'Lime',
description: 'Fresh, energetic lime with purple accents',
previewColors: { bg: '#E8F5A3', accent: '#7C3AED', darkBg: '#0F0F1A' }
},
{
id: 'ocean',
name: 'Ocean',
description: 'Calm, professional blue tones',
previewColors: { bg: '#E0F2FE', accent: '#0284C7', darkBg: '#082F49' }
},
{
id: 'retro',
name: 'Retro',
description: 'Warm, nostalgic amber vibes',
previewColors: { bg: '#FEF3C7', accent: '#D97706', darkBg: '#1C1917' }
},
{
id: 'neo',
name: 'Neo',
description: 'Modern cyberpunk pink/magenta',
previewColors: { bg: '#FDF4FF', accent: '#D946EF', darkBg: '#0F0720' }
},
{
id: 'forest',
name: 'Forest',
description: 'Natural, earthy green tones',
previewColors: { bg: '#DCFCE7', accent: '#16A34A', darkBg: '#052E16' }
}
];
@@ -5,6 +5,23 @@
import type { NotificationSettings } from './project';
import type { ChangelogFormat, ChangelogAudience, ChangelogEmojiLevel } from './changelog';
// Color theme types for multi-theme support
export type ColorTheme = 'default' | 'dusk' | 'lime' | 'ocean' | 'retro' | 'neo' | 'forest';
export interface ThemePreviewColors {
bg: string;
accent: string;
darkBg: string;
darkAccent?: string;
}
export interface ColorThemeDefinition {
id: ColorTheme;
name: string;
description: string;
previewColors: ThemePreviewColors;
}
// Thinking level for Claude model (budget token allocation)
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
@@ -44,6 +61,7 @@ export interface AgentProfile {
export interface AppSettings {
theme: 'light' | 'dark' | 'system';
colorTheme?: ColorTheme;
defaultModel: string;
agentFramework: string;
pythonPath?: string;