feat: implement light/dark mode theme system (#182)

* feat: implement light/dark mode theme system

- Add semantic color CSS variables (success, warning, info, error) for both themes
- Create ThemeProvider with React context and localStorage persistence
- Add ThemeToggle component with sun/moon icons for header region
- Add theme switcher in General Settings modal
- Replace 200+ hardcoded Tailwind colors with CSS variables across 30+ components
- Update layout.tsx to remove forced dark mode
- Keep terminal colors unchanged as requested
- Default to dark mode, with seamless light/dark switching

Components updated:
- High-priority: InstalledScriptsTab, ScriptInstallationCard, LXCSettingsModal, ScriptsGrid
- All remaining component files with hardcoded colors
- UI components: button, toggle, badge variants
- Modal components: ErrorModal, ConfirmationModal, AuthModal, SetupModal
- Form components: ServerForm, FilterBar, CategorySidebar
- Display components: ScriptCard, ScriptCardList, DiffViewer, TextViewer

Theme switchers:
- Header: Small nuanced toggle in top-right
- Settings: Detailed Light/Dark selection in General Settings

* fix: resolve ESLint warnings

- Fix missing dependencies in useCallback and useEffect hooks
- Prefix unused parameter with underscore to satisfy ESLint rules
- Build now completes without warnings

* fix: improve toggle component styling for better visibility

- Use explicit gray colors instead of CSS variables for toggle background
- Ensure proper contrast in both light and dark modes
- Toggle switches now display correctly with proper visual states

* fix: improve toggle visual states for better UX

- Use explicit conditional styling instead of peer classes
- Active toggles now clearly show primary color background
- Inactive toggles show gray background for clear distinction
- Much easier to tell which toggles are on/off at a glance

* fix: improve toggle contrast in dark mode

- Change inactive toggle background from gray-700 to gray-600 for better visibility
- Add darker border color (gray-500) for toggle handle in dark mode
- Toggles now have proper contrast against dark backgrounds
- Both light and dark modes now have clear visual distinction

* fix: resolve dependency loop and improve dropdown styling

- Fix circular dependency in InstalledScriptsTab status check
- Remove fetchContainerStatuses function and inline logic in useEffect
- Make all dropdown menu items grey with consistent hover effects
- Update both ScriptInstallationCard and InstalledScriptsTab dropdowns
- Remove unused useCallback import
- Build now completes without warnings or errors

* fix: restore proper button colors and eliminate dependency loop

- Restore red color for Stop/Destroy buttons and green for Start buttons
- Fix circular dependency by using ref for containerStatusMutation
- Update both InstalledScriptsTab and ScriptInstallationCard dropdowns
- Maintain grey color for other menu items (Update, Shell, Open UI, etc.)
- Build now completes without warnings or dependency loops

* feat: add missing hover utility classes for semantic colors

- Add hover states for success, warning, info, error colors
- Add hover:bg-success/20, hover:bg-error/20, etc. classes
- Add hover:text-success-foreground, hover:text-error-foreground classes
- Start/Stop and Destroy buttons now have proper hover effects
- All dropdown menu items now have consistent hover behavior

* feat: improve status cards with useful LXC container information

- Replace useless 'Successful/Failed/In Progress' cards with meaningful data
- Show 'Running LXC' count in green (actual running containers)
- Show 'Stopped LXC' count in red (actual stopped containers)
- Keep 'Total Installations' for overall count
- Change layout from 4 columns to 3 columns for better spacing
- Status cards now show real-time container states instead of installation status

* style: center content in status cards

- Add text-center class to each individual status card
- Numbers and labels now centered within each card
- Improves visual balance and readability
- All three cards (Total, Running LXC, Stopped LXC) now have centered content
This commit is contained in:
Michel Roegl-Brunner
2025-10-17 15:26:59 +02:00
committed by GitHub
parent d0312165bd
commit c89638021c
223 changed files with 1420 additions and 1037 deletions

View File

@@ -146,14 +146,14 @@ export function ScriptInstallationCard({
{script.container_status && (
<div className="flex items-center space-x-1">
<div className={`w-2 h-2 rounded-full ${
script.container_status === 'running' ? 'bg-green-500' :
script.container_status === 'stopped' ? 'bg-red-500' :
'bg-gray-400'
script.container_status === 'running' ? 'bg-success' :
script.container_status === 'stopped' ? 'bg-error' :
'bg-muted-foreground'
}`}></div>
<span className={`text-xs font-medium ${
script.container_status === 'running' ? 'text-green-700 dark:text-green-300' :
script.container_status === 'stopped' ? 'text-red-700 dark:text-red-300' :
'text-gray-500 dark:text-gray-400'
script.container_status === 'running' ? 'text-success' :
script.container_status === 'stopped' ? 'text-error' :
'text-muted-foreground'
}`}>
{script.container_status === 'running' ? 'Running' :
script.container_status === 'stopped' ? 'Stopped' :
@@ -195,7 +195,7 @@ export function ScriptInstallationCard({
<button
onClick={onOpenWebUI}
disabled={containerStatus === 'stopped'}
className={`text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:underline flex-shrink-0 ${
className={`text-info hover:text-info/80 hover:underline flex-shrink-0 ${
containerStatus === 'stopped' ? 'opacity-50 cursor-not-allowed' : ''
}`}
>
@@ -205,7 +205,7 @@ export function ScriptInstallationCard({
<button
onClick={onAutoDetectWebUI}
disabled={isAutoDetecting}
className="text-xs px-2 py-1 bg-blue-900 hover:bg-blue-800 text-blue-300 border border-blue-700 rounded disabled:opacity-50 transition-colors flex-shrink-0 ml-2"
className="text-xs px-2 py-1 bg-info hover:bg-info/90 text-info-foreground border border-info rounded disabled:opacity-50 transition-colors flex-shrink-0 ml-2"
title="Re-detect IP and port"
>
{isAutoDetecting ? '...' : 'Re-detect'}
@@ -219,7 +219,7 @@ export function ScriptInstallationCard({
<button
onClick={onAutoDetectWebUI}
disabled={isAutoDetecting}
className="text-xs px-2 py-1 bg-blue-900 hover:bg-blue-800 text-blue-300 border border-blue-700 rounded disabled:opacity-50 transition-colors"
className="text-xs px-2 py-1 bg-info hover:bg-info/90 text-info-foreground border border-info rounded disabled:opacity-50 transition-colors"
title="Re-detect IP and port"
>
{isAutoDetecting ? '...' : 'Re-detect'}
@@ -292,17 +292,17 @@ export function ScriptInstallationCard({
<Button
variant="outline"
size="sm"
className="flex-1 min-w-0 bg-gray-800/20 hover:bg-gray-800/30 border border-gray-600/50 text-gray-300 hover:text-gray-200 hover:border-gray-500/60 transition-all duration-200 hover:scale-105 hover:shadow-md"
className="flex-1 min-w-0 bg-muted/20 hover:bg-muted/30 border border-muted text-muted-foreground hover:text-foreground hover:border-muted-foreground transition-all duration-200 hover:scale-105 hover:shadow-md"
>
Actions
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-48 bg-gray-900 border-gray-700">
<DropdownMenuContent className="w-48 bg-card border-border">
{script.container_id && (
<DropdownMenuItem
onClick={onUpdate}
disabled={containerStatus === 'stopped'}
className="text-cyan-300 hover:text-cyan-200 hover:bg-cyan-900/20 focus:bg-cyan-900/20"
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
>
Update
</DropdownMenuItem>
@@ -311,7 +311,7 @@ export function ScriptInstallationCard({
<DropdownMenuItem
onClick={onShell}
disabled={containerStatus === 'stopped'}
className="text-gray-300 hover:text-gray-200 hover:bg-gray-800/20 focus:bg-gray-800/20"
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
>
Shell
</DropdownMenuItem>
@@ -320,20 +320,20 @@ export function ScriptInstallationCard({
<DropdownMenuItem
onClick={onOpenWebUI}
disabled={containerStatus === 'stopped'}
className="text-blue-300 hover:text-blue-200 hover:bg-blue-900/20 focus:bg-blue-900/20"
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
>
Open UI
</DropdownMenuItem>
)}
{script.container_id && script.execution_mode === 'ssh' && (
<>
<DropdownMenuSeparator className="bg-gray-700" />
<DropdownMenuSeparator className="bg-border" />
<DropdownMenuItem
onClick={() => onStartStop(containerStatus === 'running' ? 'stop' : 'start')}
disabled={isControlling || containerStatus === 'unknown'}
className={containerStatus === 'running'
? "text-red-300 hover:text-red-200 hover:bg-red-900/20 focus:bg-red-900/20"
: "text-green-300 hover:text-green-200 hover:bg-green-900/20 focus:bg-green-900/20"
? "text-error hover:text-error-foreground hover:bg-error/20 focus:bg-error/20"
: "text-success hover:text-success-foreground hover:bg-success/20 focus:bg-success/20"
}
>
{isControlling ? 'Working...' : containerStatus === 'running' ? 'Stop' : 'Start'}
@@ -341,7 +341,7 @@ export function ScriptInstallationCard({
<DropdownMenuItem
onClick={onDestroy}
disabled={isControlling}
className="text-red-300 hover:text-red-200 hover:bg-red-900/20 focus:bg-red-900/20"
className="text-error hover:text-error-foreground hover:bg-error/20 focus:bg-error/20"
>
{isControlling ? 'Working...' : 'Destroy'}
</DropdownMenuItem>
@@ -349,11 +349,11 @@ export function ScriptInstallationCard({
)}
{(!script.container_id || script.execution_mode !== 'ssh') && (
<>
<DropdownMenuSeparator className="bg-gray-700" />
<DropdownMenuSeparator className="bg-border" />
<DropdownMenuItem
onClick={onDelete}
disabled={isDeleting}
className="text-red-300 hover:text-red-200 hover:bg-red-900/20 focus:bg-red-900/20"
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
>
{isDeleting ? 'Deleting...' : 'Delete'}
</DropdownMenuItem>