From e6a70e3a02af1ebbec92e839f05b4f60559f498a Mon Sep 17 00:00:00 2001 From: Peet McKinney <68706879+PeetMcK@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:35:49 -0700 Subject: [PATCH] feat: Complete light mode implementation with semantic theme system (#450) - Add comprehensive light/dark mode CSS variables with semantic naming - Implement theme-aware scrollbars using CSS variables - Add light mode backgrounds: --bg-base, --bg-elevated, --bg-surface, etc. - Add theme-aware borders: --border-base, --border-panel, --border-subtle - Add semantic text colors: --foreground-secondary, --foreground-subtle - Convert oklch colors to hex for better compatibility - Add theme awareness to CodeMirror editors - Update dark mode colors for consistency (background, sidebar, card, muted, input) - Add Tailwind color mappings for semantic classes Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> --- package-lock.json | 32 +++ package.json | 1 + src/components/ui/badge.tsx | 2 +- src/components/ui/button.tsx | 2 +- src/components/ui/command.tsx | 2 +- src/components/ui/input.tsx | 2 +- src/components/ui/resizable.tsx | 4 +- src/components/ui/select.tsx | 2 +- src/components/ui/sidebar.tsx | 2 +- src/components/ui/table.tsx | 2 +- src/constants/terminal-themes.ts | 57 ++++ src/index.css | 269 ++++++++++++------ src/main.tsx | 6 +- src/ui/desktop/DesktopApp.tsx | 32 ++- src/ui/desktop/admin/AdminSettings.tsx | 96 +++---- src/ui/desktop/admin/RoleManagement.tsx | 6 +- src/ui/desktop/admin/UserEditDialog.tsx | 2 +- .../apps/command-palette/CommandPalette.tsx | 16 +- .../apps/credentials/CredentialEditor.tsx | 34 ++- .../apps/credentials/CredentialSelector.tsx | 2 +- .../apps/credentials/CredentialViewer.tsx | 2 +- .../apps/credentials/CredentialsManager.tsx | 6 +- src/ui/desktop/apps/dashboard/Dashboard.tsx | 52 ++-- .../desktop/apps/dashboard/apps/UpdateLog.tsx | 30 +- .../docker/components/ContainerDetail.tsx | 4 +- .../apps/docker/components/ContainerList.tsx | 2 +- .../apps/docker/components/ContainerStats.tsx | 2 +- .../apps/docker/components/LogViewer.tsx | 2 +- .../apps/file-manager/DragIndicator.tsx | 4 +- .../desktop/apps/file-manager/FileManager.tsx | 10 +- .../file-manager/FileManagerContextMenu.tsx | 6 +- .../apps/file-manager/FileManagerGrid.tsx | 20 +- .../apps/file-manager/FileManagerSidebar.tsx | 22 +- .../components/CompressDialog.tsx | 8 +- .../file-manager/components/DiffViewer.tsx | 8 +- .../file-manager/components/FileViewer.tsx | 30 +- .../components/PermissionsDialog.tsx | 6 +- .../desktop/apps/host-manager/HostManager.tsx | 16 +- .../apps/host-manager/HostManagerEditor.tsx | 60 ++-- .../apps/host-manager/HostManagerViewer.tsx | 2 +- .../apps/host-manager/HostSharingTab.tsx | 4 +- .../components/FolderEditDialog.tsx | 8 +- .../desktop/apps/server-stats/ServerStats.tsx | 18 +- .../apps/server-stats/widgets/CpuWidget.tsx | 9 +- .../apps/server-stats/widgets/DiskWidget.tsx | 9 +- .../server-stats/widgets/LoginStatsWidget.tsx | 34 ++- .../server-stats/widgets/MemoryWidget.tsx | 9 +- .../server-stats/widgets/NetworkWidget.tsx | 19 +- .../server-stats/widgets/ProcessesWidget.tsx | 28 +- .../server-stats/widgets/SystemWidget.tsx | 17 +- .../server-stats/widgets/UptimeWidget.tsx | 9 +- src/ui/desktop/apps/terminal/Terminal.tsx | 49 +++- .../desktop/apps/terminal/TerminalPreview.tsx | 32 ++- .../command-history/CommandAutocomplete.tsx | 10 +- src/ui/desktop/apps/tools/SSHToolsSidebar.tsx | 78 ++--- src/ui/desktop/apps/tunnel/TunnelViewer.tsx | 2 +- src/ui/desktop/authentication/Auth.tsx | 99 +++---- .../authentication/ElectronLoginForm.tsx | 6 +- src/ui/desktop/navigation/AppView.tsx | 65 +++-- src/ui/desktop/navigation/LeftSidebar.tsx | 20 +- src/ui/desktop/navigation/SSHAuthDialog.tsx | 6 +- src/ui/desktop/navigation/TOTPDialog.tsx | 4 +- src/ui/desktop/navigation/TopNavbar.tsx | 12 +- .../navigation/animations/SimpleLoader.tsx | 8 +- .../desktop/navigation/hosts/FolderCard.tsx | 4 +- src/ui/desktop/navigation/hosts/Host.tsx | 18 +- src/ui/desktop/navigation/tabs/Tab.tsx | 10 +- .../desktop/navigation/tabs/TabDropdown.tsx | 8 +- src/ui/desktop/user/ElectronVersionCheck.tsx | 6 +- src/ui/desktop/user/PasswordReset.tsx | 2 +- src/ui/desktop/user/UserProfile.tsx | 149 ++++++---- src/ui/mobile/MobileApp.tsx | 12 +- .../mobile/apps/navigation/BottomNavbar.tsx | 8 +- src/ui/mobile/apps/navigation/LeftSidebar.tsx | 6 +- .../apps/navigation/hosts/FolderCard.tsx | 4 +- src/ui/mobile/apps/navigation/hosts/Host.tsx | 4 +- src/ui/mobile/apps/terminal/Terminal.tsx | 32 ++- .../mobile/apps/terminal/TerminalKeyboard.tsx | 8 +- .../mobile/apps/terminal/kb-light-theme.css | 29 ++ src/ui/mobile/authentication/Auth.tsx | 8 +- src/ui/mobile/navigation/BottomNavbar.tsx | 8 +- src/ui/mobile/navigation/LeftSidebar.tsx | 6 +- src/ui/mobile/navigation/hosts/FolderCard.tsx | 4 +- src/ui/mobile/navigation/hosts/Host.tsx | 4 +- 84 files changed, 1084 insertions(+), 664 deletions(-) create mode 100644 src/ui/mobile/apps/terminal/kb-light-theme.css diff --git a/package-lock.json b/package-lock.json index 8c64a2de..1eaa54ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "@types/qrcode": "^1.5.5", "@types/speakeasy": "^2.0.10", "@uiw/codemirror-extensions-langs": "^4.24.1", + "@uiw/codemirror-theme-github": "^4.25.4", "@uiw/react-codemirror": "^4.24.1", "@xterm/addon-clipboard": "^0.1.0", "@xterm/addon-fit": "^0.10.0", @@ -5794,6 +5795,37 @@ "@codemirror/language-data": ">=6.0.0" } }, + "node_modules/@uiw/codemirror-theme-github": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.25.4.tgz", + "integrity": "sha512-M5zRT2vIpNsuKN0Lz+DwLnmhHW8Eddp1M9zC0hm3V+bvffmaSn/pUDey1eqGIv5xNNmjhqvDAz0a90xLYCzvSw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.4" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.4.tgz", + "integrity": "sha512-2SLktItgcZC4p0+PfFusEbAHwbuAWe3bOOntCevVgHtrWGtGZX3IPv2k8IKZMgOXtAHyGKpJvT9/nspPn/uCQg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, "node_modules/@uiw/react-codemirror": { "version": "4.25.2", "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.2.tgz", diff --git a/package.json b/package.json index d39360eb..b34f9e5d 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/qrcode": "^1.5.5", "@types/speakeasy": "^2.0.10", "@uiw/codemirror-extensions-langs": "^4.24.1", + "@uiw/codemirror-theme-github": "^4.25.4", "@uiw/react-codemirror": "^4.24.1", "@xterm/addon-clipboard": "^0.1.0", "@xterm/addon-fit": "^0.10.0", diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index b99be47d..dbc4719e 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -15,7 +15,7 @@ const badgeVariants = cva( secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", destructive: - "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + "border-transparent bg-destructive text-foreground [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", }, diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index fbdf6b8d..849574a3 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -13,7 +13,7 @@ const buttonVariants = cva( default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + "bg-destructive text-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index ee7450af..fb265605 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -90,7 +90,7 @@ function CommandList({ ) { type={type} data-slot="input" className={cn( - "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] duration-200 outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground bg-elevated dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] duration-200 outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className, diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx index 7909c556..2835db13 100644 --- a/src/components/ui/resizable.tsx +++ b/src/components/ui/resizable.tsx @@ -37,13 +37,13 @@ function ResizableHandle({ div]:rotate-90 bg-dark-border-hover hover:bg-dark-active active:bg-dark-pressed transition-colors duration-150", + "relative flex w-1 items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-1 data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90 bg-edge-hover hover:bg-interact active:bg-pressed transition-colors duration-150", className, )} {...props} > {withHandle && ( -
+
)} diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 0c883e37..dadd3525 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -59,7 +59,7 @@ function SelectContent({ ) { data-slot="sidebar-content" data-sidebar="content" className={cn( - "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", + "flex min-h-0 flex-1 flex-col gap-2 overflow-auto thin-scrollbar group-data-[collapsible=icon]:overflow-hidden", className, )} {...props} diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 2ad27ce8..a198ece0 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -6,7 +6,7 @@ function Table({ className, ...props }: React.ComponentProps<"table">) { return (
= { + // Legacy "termix" theme - auto-switches between termixDark and termixLight based on app theme termix: { name: "Termix Default", category: "dark", @@ -56,6 +57,62 @@ export const TERMINAL_THEMES: Record = { }, }, + termixDark: { + name: "Termix Dark", + category: "dark", + colors: { + background: "#18181b", + foreground: "#f7f7f7", + cursor: "#f7f7f7", + cursorAccent: "#18181b", + selectionBackground: "#3a3a3d", + black: "#2e3436", + red: "#cc0000", + green: "#4e9a06", + yellow: "#c4a000", + blue: "#3465a4", + magenta: "#75507b", + cyan: "#06989a", + white: "#d3d7cf", + brightBlack: "#555753", + brightRed: "#ef2929", + brightGreen: "#8ae234", + brightYellow: "#fce94f", + brightBlue: "#729fcf", + brightMagenta: "#ad7fa8", + brightCyan: "#34e2e2", + brightWhite: "#eeeeec", + }, + }, + + termixLight: { + name: "Termix Light", + category: "light", + colors: { + background: "#ffffff", + foreground: "#18181b", + cursor: "#18181b", + cursorAccent: "#ffffff", + selectionBackground: "#d1d5db", + black: "#18181b", + red: "#dc2626", + green: "#16a34a", + yellow: "#ca8a04", + blue: "#2563eb", + magenta: "#9333ea", + cyan: "#0891b2", + white: "#f4f4f5", + brightBlack: "#71717a", + brightRed: "#ef4444", + brightGreen: "#22c55e", + brightYellow: "#eab308", + brightBlue: "#3b82f6", + brightMagenta: "#a855f7", + brightCyan: "#06b6d4", + brightWhite: "#ffffff", + }, + }, + dracula: { name: "Dracula", category: "dark", diff --git a/src/index.css b/src/index.css index c833a548..ce974a18 100644 --- a/src/index.css +++ b/src/index.css @@ -8,45 +8,77 @@ font-weight: 400; color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #09090b; + color: var(--foreground); + background-color: var(--bg-base); font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.141 0.005 285.823); - --card: oklch(1 0 0); - --card-foreground: oklch(0.141 0.005 285.823); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.21 0.006 285.885); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.705 0.015 286.067); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.21 0.006 285.885); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.705 0.015 286.067); + --background: #ffffff; + --foreground: #18181b; + --card: #ffffff; + --card-foreground: #18181b; + --popover: #ffffff; + --popover-foreground: #18181b; + --primary: #27272a; + --primary-foreground: #fafafa; + --secondary: #f4f4f5; + --secondary-foreground: #27272a; + --muted: #f4f4f5; + --muted-foreground: #71717a; + --accent: #f4f4f5; + --accent-foreground: #27272a; + --destructive: #dc2626; + --border: #e4e4e7; + --input: #e4e4e7; + --ring: #a1a1aa; + --chart-1: #e76e50; + --chart-2: #2a9d8f; + --chart-3: #264653; + --chart-4: #e9c46a; + --chart-5: #f4a261; + --sidebar: #f9f9f9; + --sidebar-foreground: #18181b; + --sidebar-primary: #27272a; + --sidebar-primary-foreground: #fafafa; + --sidebar-accent: #f4f4f5; + --sidebar-accent-foreground: #27272a; + --sidebar-border: #e4e4e7; + --sidebar-ring: #a1a1aa; + + /* NEW SEMANTIC VARIABLES - Light Mode Backgrounds */ + --bg-base: #fcfcfc; + --bg-elevated: #ffffff; + --bg-surface: #f3f4f6; + --bg-surface-hover: #e5e7eb; /* Panel hover - replaces dark-bg-panel-hover */ + --bg-input: #ffffff; + --bg-deepest: #e5e7eb; + --bg-header: #eeeeef; + --bg-button: #f3f4f6; + --bg-active: #e5e7eb; + --bg-light: #fafafa; /* Light background - replaces dark-bg-light */ + --bg-subtle: #f5f5f5; /* Very light background - replaces dark-bg-very-light */ + --bg-interact: #d1d5db; /* Interactive/active state - replaces dark-active */ + --border-base: #e5e7eb; + --border-panel: #d1d5db; + --border-subtle: #f3f4f6; + --border-medium: #d1d5db; + --bg-hover: #f3f4f6; + --bg-hover-alt: #e5e7eb; + --bg-pressed: #d1d5db; + --border-hover: #d1d5db; + --border-active: #9ca3af; + + /* NEW SEMANTIC VARIABLES - Light Mode Text Colors */ + --foreground-secondary: #334155; + --foreground-subtle: #94a3b8; + + /* Scrollbar Colors - Light Mode */ + --scrollbar-thumb: #c1c1c3; + --scrollbar-thumb-hover: #a1a1a3; + --scrollbar-track: #f3f4f6; } @theme inline { @@ -107,40 +139,101 @@ --color-dark-bg-panel: #1b1b1e; --color-dark-border-panel: #222224; --color-dark-bg-panel-hover: #232327; + + /* NEW SEMANTIC COLOR MAPPINGS - Creates Tailwind classes */ + /* Backgrounds: bg-canvas, bg-elevated, bg-surface, etc. */ + --color-canvas: var(--bg-base); + --color-elevated: var(--bg-elevated); + --color-surface: var(--bg-surface); + --color-surface-hover: var(--bg-surface-hover); + --color-field: var(--bg-input); + --color-deepest: var(--bg-deepest); + --color-header: var(--bg-header); + --color-button: var(--bg-button); + --color-active: var(--bg-active); + --color-light: var(--bg-light); + --color-subtle: var(--bg-subtle); + --color-interact: var(--bg-interact); + --color-hover: var(--bg-hover); + --color-hover-alt: var(--bg-hover-alt); + --color-pressed: var(--bg-pressed); + /* Borders: border-edge, border-edge-panel, etc. */ + --color-edge: var(--border-base); + --color-edge-panel: var(--border-panel); + --color-edge-subtle: var(--border-subtle); + --color-edge-medium: var(--border-medium); + --color-edge-hover: var(--border-hover); + --color-edge-active: var(--border-active); + + /* NEW SEMANTIC TEXT COLOR MAPPINGS - Creates Tailwind text classes */ + --color-foreground-secondary: var(--foreground-secondary); + --color-foreground-subtle: var(--foreground-subtle); } .dark { - --background: oklch(0.141 0.005 285.823); - --foreground: oklch(0.985 0 0); - --card: oklch(0.21 0.006 285.885); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.21 0.006 285.885); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.92 0.004 286.32); - --primary-foreground: oklch(0.21 0.006 285.885); - --secondary: oklch(0.274 0.006 286.033); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.552 0.016 285.938); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.552 0.016 285.938); + --background: #09090b; + --foreground: #fafafa; + --card: #18181b; + --card-foreground: #fafafa; + --popover: #27272a; + --popover-foreground: #fafafa; + --primary: #e4e4e7; + --primary-foreground: #27272a; + --secondary: #3f3f46; + --secondary-foreground: #fafafa; + --muted: #27272a; + --muted-foreground: #9ca3af; + --accent: #3f3f46; + --accent-foreground: #fafafa; + --destructive: #f87171; + --border: #ffffff1a; + --input: #ffffff26; + --ring: #71717a; + --chart-1: #3b82f6; + --chart-2: #34d399; + --chart-3: #f4a261; + --chart-4: #a855f7; + --chart-5: #f43f5e; + --sidebar: #18181b; + --sidebar-foreground: #fafafa; + --sidebar-primary: #3b82f6; + --sidebar-primary-foreground: #fafafa; + --sidebar-accent: #3f3f46; + --sidebar-accent-foreground: #fafafa; + --sidebar-border: #ffffff1a; + --sidebar-ring: #71717a; + + /* NEW SEMANTIC VARIABLES - Dark Mode Background Overrides */ + --bg-base: #18181b; + --bg-elevated: #0e0e10; + --bg-surface: #1b1b1e; + --bg-surface-hover: #232327; /* Panel hover */ + --bg-input: #222225; + --bg-deepest: #09090b; + --bg-header: #131316; + --bg-button: #23232a; + --bg-active: #1d1d1f; + --bg-light: #141416; /* Light background */ + --bg-subtle: #101014; /* Very light background */ + --bg-interact: #2a2a2c; /* Interactive/active state */ + --border-base: #303032; + --border-panel: #222224; + --border-subtle: #5a5a5d; + --border-medium: #373739; + --bg-hover: #2d2d30; + --bg-hover-alt: #2a2a2d; + --bg-pressed: #1a1a1c; + --border-hover: #434345; + --border-active: #2d2d30; + + /* NEW SEMANTIC VARIABLES - Dark Mode Text Color Overrides */ + --foreground-secondary: #d1d5db; /* Matches text-gray-300 */ + --foreground-subtle: #6b7280; /* Matches text-gray-500 */ + + /* Scrollbar Colors - Dark Mode */ + --scrollbar-thumb: #434345; + --scrollbar-thumb-hover: #5a5a5d; + --scrollbar-track: #18181b; } @layer base { @@ -158,25 +251,10 @@ } } +/* Thin Scrollbar - Theme Aware */ .thin-scrollbar { scrollbar-width: thin; - scrollbar-color: #303032 transparent; -} - -.thin-scrollbar::-webkit-scrollbar { - height: 6px; - width: 6px; -} - -.thin-scrollbar::-webkit-scrollbar-track { - background: transparent; -} - -.thin-scrollbar::-webkit-scrollbar-thumb { - background-color: #303032; - border-radius: 9999px; - border: 2px solid transparent; - background-clip: content-box; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); } .thin-scrollbar::-webkit-scrollbar { @@ -185,19 +263,38 @@ } .thin-scrollbar::-webkit-scrollbar-track { - background: #18181b; + background: var(--scrollbar-track); } .thin-scrollbar::-webkit-scrollbar-thumb { - background: #434345; + background: var(--scrollbar-thumb); border-radius: 3px; } .thin-scrollbar::-webkit-scrollbar-thumb:hover { - background: #5a5a5d; + background: var(--scrollbar-thumb-hover); } -.thin-scrollbar { +/* Skinny scrollbar - even thinner variant */ +.skinny-scrollbar { scrollbar-width: thin; - scrollbar-color: #434345 #18181b; + scrollbar-color: var(--scrollbar-thumb) transparent; +} + +.skinny-scrollbar::-webkit-scrollbar { + width: 4px; + height: 4px; +} + +.skinny-scrollbar::-webkit-scrollbar-track { + background: transparent; +} + +.skinny-scrollbar::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 2px; +} + +.skinny-scrollbar::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); } diff --git a/src/main.tsx b/src/main.tsx index 53ff210e..0202bb43 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -79,13 +79,13 @@ function RootApp() {
("idle"); const { currentTab, tabs } = useTabs(); const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false); + const { theme, setTheme } = useTheme(); const [rightSidebarOpen, setRightSidebarOpen] = useState(false); const [rightSidebarWidth, setRightSidebarWidth] = useState(400); const lastShiftPressTime = useRef(0); + // DEBUG: Theme toggle - double-tap left Alt/Option to toggle light/dark mode + // Comment out the next line and the AltLeft handler below to disable + const lastAltPressTime = useRef(0); + useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.code === "ShiftLeft") { @@ -49,6 +55,26 @@ function AppContent() { lastShiftPressTime.current = now; } } + + // DEBUG: Double-tap left Alt/Option to toggle light/dark theme + // Remove or comment out this block for production + /* DEBUG_THEME_TOGGLE_START */ + if (event.code === "AltLeft" && !event.repeat) { + const now = Date.now(); + if (now - lastAltPressTime.current < 300) { + // Use setTheme to properly update React state (not just DOM class) + const currentIsDark = theme === "dark" || + (theme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches); + const newTheme = currentIsDark ? "light" : "dark"; + setTheme(newTheme); + console.log("[DEBUG] Theme toggled:", newTheme); + lastAltPressTime.current = 0; + } else { + lastAltPressTime.current = now; + } + } + /* DEBUG_THEME_TOGGLE_END */ + if (event.key === "Escape") { setIsCommandPaletteOpen(false); } @@ -58,7 +84,7 @@ function AppContent() { return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, []); + }, [theme, setTheme]); useEffect(() => { const checkAuth = () => { @@ -166,7 +192,7 @@ function AppContent() { if (authLoading) { return (
+
@@ -758,69 +758,69 @@ export function AdminSettings({
-
+
- + {t("admin.general")} - + OIDC - + {t("admin.users")} - + Sessions - + {t("rbac.roles.label")} - + {t("admin.databaseSecurity")} -
+

{t("admin.userRegistration")}

- - + +
-
+

{t("admin.externalAuthentication")}

@@ -1075,7 +1075,7 @@ export function AdminSettings({ -
+

{t("admin.userManagement")} @@ -1187,7 +1187,7 @@ export function AdminSettings({ -
+

{t("admin.sessionManagement")}

{recentActivityLoading ? (
@@ -632,7 +626,7 @@ export function Dashboard({
-
-
+
+

{t("dashboard.quickActions")}

-
+
-
+

{t("dashboard.serverStats")}

{serverStatsLoading ? (
@@ -738,7 +732,7 @@ export function Dashboard({
-
+
{versionInfo && versionInfo.status === "requires_update" && ( - - + + {t("common.updateAvailable")} - + {t("common.newVersionAvailable", { version: versionInfo.version, })} @@ -161,11 +161,11 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) { {releases?.items.map((release) => (
window.open(release.link, "_blank")} >
-

+

{release.title}

{release.isPrerelease && ( @@ -175,11 +175,11 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) { )}
-

+

{formatDescription(release.description)}

-
+
{new Date(release.pubDate).toLocaleDateString()} @@ -198,11 +198,11 @@ export function UpdateLog({ loggedIn }: UpdateLogProps) {
{releases && releases.items.length === 0 && !loading && ( - - + + {t("common.noReleases")} - + {t("common.noReleasesFound")} diff --git a/src/ui/desktop/apps/docker/components/ContainerDetail.tsx b/src/ui/desktop/apps/docker/components/ContainerDetail.tsx index 75dff30b..13e7980e 100644 --- a/src/ui/desktop/apps/docker/components/ContainerDetail.tsx +++ b/src/ui/desktop/apps/docker/components/ContainerDetail.tsx @@ -78,7 +78,7 @@ export function ContainerDetail({
) : ( -
+
{filteredContainers.map((container) => ( +
{/* CPU Usage */} diff --git a/src/ui/desktop/apps/docker/components/LogViewer.tsx b/src/ui/desktop/apps/docker/components/LogViewer.tsx index d8ecc4bd..e185a7b3 100644 --- a/src/ui/desktop/apps/docker/components/LogViewer.tsx +++ b/src/ui/desktop/apps/docker/components/LogViewer.tsx @@ -230,7 +230,7 @@ export function LogViewer({
) : ( -
+
                 {filteredLogs || (
                   No logs available
diff --git a/src/ui/desktop/apps/file-manager/DragIndicator.tsx b/src/ui/desktop/apps/file-manager/DragIndicator.tsx
index 6aedf278..19c33e43 100644
--- a/src/ui/desktop/apps/file-manager/DragIndicator.tsx
+++ b/src/ui/desktop/apps/file-manager/DragIndicator.tsx
@@ -79,7 +79,7 @@ export function DragIndicator({
     
{(isDownloading || isDragging) && !error && ( -
+
-
+
+
-

{currentHost.name}

+

{currentHost.name}

{currentHost.ip}:{currentHost.port} @@ -1956,11 +1956,11 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { placeholder={t("fileManager.searchFiles")} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} - className="pl-8 w-48 h-9 bg-dark-bg-button border-dark-border" + className="pl-8 w-48 h-9 bg-button border-edge" />
-
+
-
+
{t("fileManager.itemCount", { count: files.length })} {selectedFiles.length > 0 && ( diff --git a/src/ui/desktop/apps/file-manager/FileManagerSidebar.tsx b/src/ui/desktop/apps/file-manager/FileManagerSidebar.tsx index fc63779d..77a75e37 100644 --- a/src/ui/desktop/apps/file-manager/FileManagerSidebar.tsx +++ b/src/ui/desktop/apps/file-manager/FileManagerSidebar.tsx @@ -375,9 +375,9 @@ export function FileManagerSidebar({
handleItemClick(item)} @@ -397,7 +397,7 @@ export function FileManagerSidebar({ e.stopPropagation(); toggleFolder(item.id, item.path); }} - className="p-0.5 hover:bg-dark-hover rounded" + className="p-0.5 hover:bg-hover rounded" > {isExpanded ? ( @@ -454,7 +454,7 @@ export function FileManagerSidebar({ return ( <> -
+
{renderSection( @@ -475,7 +475,7 @@ export function FileManagerSidebar({
@@ -495,7 +495,7 @@ export function FileManagerSidebar({
{recentItems.length > 1 && ( <> -
+
-
-

+

+

{t("fileManager.selectedFiles")}:

    @@ -134,7 +134,7 @@ export function CompressDialog({ ))} {fileNames.length > 5 && ( -
  • +
  • {t("fileManager.andMoreFiles", { count: fileNames.length - 5, })} diff --git a/src/ui/desktop/apps/file-manager/components/DiffViewer.tsx b/src/ui/desktop/apps/file-manager/components/DiffViewer.tsx index 70cf8d3a..ca74c3f5 100644 --- a/src/ui/desktop/apps/file-manager/components/DiffViewer.tsx +++ b/src/ui/desktop/apps/file-manager/components/DiffViewer.tsx @@ -210,7 +210,7 @@ export function DiffViewer({ if (isLoading) { return ( -
    +

    @@ -223,7 +223,7 @@ export function DiffViewer({ if (error) { return ( -

    +

    {error}

    @@ -237,8 +237,8 @@ export function DiffViewer({ } return ( -
    -
    +
    +
    diff --git a/src/ui/desktop/apps/file-manager/components/FileViewer.tsx b/src/ui/desktop/apps/file-manager/components/FileViewer.tsx index a9b837e8..00186a04 100644 --- a/src/ui/desktop/apps/file-manager/components/FileViewer.tsx +++ b/src/ui/desktop/apps/file-manager/components/FileViewer.tsx @@ -133,14 +133,14 @@ function getLanguageIcon(filename: string): React.ReactNode { yml: , toml: , sql: , - sh: , - bash: , - zsh: , + sh: , + bash: , + zsh: , vue: , svelte: , - md: , - conf: , - ini: , + md: , + conf: , + ini: , }; return iconMap[ext] || ; @@ -238,7 +238,7 @@ function getFileType(filename: string): { return { type: "unknown", icon: , - color: "text-gray-500", + color: "text-foreground-subtle", }; } } @@ -423,7 +423,7 @@ export function FileViewer({
    -

    Loading file...

    +

    Loading file...

    ); @@ -812,6 +812,8 @@ export function FileViewer({ }, ".cm-scroller": { overflow: "auto", + scrollbarWidth: "thin", + scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)", }, ".cm-editor": { height: "100%", @@ -835,7 +837,7 @@ export function FileViewer({ }} /> ) : ( -
    +
    {editedContent || content || t("fileManager.fileIsEmpty")}
    )} @@ -969,7 +971,7 @@ export function FileViewer({
    -
    +
    ), table: ({ children }) => ( -
    +

{children}
@@ -1084,7 +1086,7 @@ export function FileViewer({
) : ( -
+
), table: ({ children }) => ( -
+
{children}
@@ -1252,7 +1254,7 @@ export function FileViewer({
-
+
{pdfError ? (
diff --git a/src/ui/desktop/apps/file-manager/components/PermissionsDialog.tsx b/src/ui/desktop/apps/file-manager/components/PermissionsDialog.tsx index 2d88a4f2..40364cef 100644 --- a/src/ui/desktop/apps/file-manager/components/PermissionsDialog.tsx +++ b/src/ui/desktop/apps/file-manager/components/PermissionsDialog.tsx @@ -140,7 +140,7 @@ export function PermissionsDialog({ return ( - + @@ -155,7 +155,7 @@ export function PermissionsDialog({
-
-