feat: Complete light mode implementation with semantic theme system

- 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
This commit is contained in:
Peet McKinney
2025-12-23 12:28:35 -07:00
parent ae6b165e37
commit 8579e42002
84 changed files with 1091 additions and 671 deletions

View File

@@ -140,7 +140,7 @@ function JumpHostItem({
<Command>
<CommandInput placeholder={t("hosts.searchServers")} />
<CommandEmpty>{t("hosts.noServerFound")}</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{hosts
.filter((h) => !editingHost || h.id !== editingHost.id)
.map((host) => (
@@ -244,7 +244,7 @@ function QuickActionItem({
<Command>
<CommandInput placeholder={t("hosts.searchSnippets")} />
<CommandEmpty>{t("hosts.noSnippetFound")}</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
{snippets.map((snippet) => (
<CommandItem
key={snippet.id}
@@ -1157,19 +1157,19 @@ export function HostManagerEditor({
onValueChange={setActiveTab}
className="w-full"
>
<TabsList>
<TabsTrigger value="general">
<TabsList className="bg-button border border-edge-medium">
<TabsTrigger value="general" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.general")}
</TabsTrigger>
<TabsTrigger value="terminal">
<TabsTrigger value="terminal" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.terminal")}
</TabsTrigger>
<TabsTrigger value="docker">Docker</TabsTrigger>
<TabsTrigger value="tunnel">{t("hosts.tunnel")}</TabsTrigger>
<TabsTrigger value="file_manager">
<TabsTrigger value="docker" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">Docker</TabsTrigger>
<TabsTrigger value="tunnel" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">{t("hosts.tunnel")}</TabsTrigger>
<TabsTrigger value="file_manager" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.fileManager")}
</TabsTrigger>
<TabsTrigger value="statistics">
<TabsTrigger value="statistics" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.statistics")}
</TabsTrigger>
{!editingHost?.isShared && (
@@ -1305,7 +1305,7 @@ export function HostManagerEditor({
{folderDropdownOpen && filteredFolders.length > 0 && (
<div
ref={folderDropdownRef}
className="absolute top-full left-0 z-50 mt-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute top-full left-0 z-50 mt-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{filteredFolders.map((folder) => (
@@ -1334,7 +1334,7 @@ export function HostManagerEditor({
<FormItem className="col-span-10 overflow-visible">
<FormLabel>{t("hosts.tags")}</FormLabel>
<FormControl>
<div className="flex flex-wrap items-center gap-1 border border-input rounded-md px-3 py-2 bg-dark-bg-input focus-within:ring-2 ring-ring min-h-[40px]">
<div className="flex flex-wrap items-center gap-1 border border-input rounded-md px-3 py-2 bg-field focus-within:ring-2 ring-ring min-h-[40px]">
{field.value.map((tag: string, idx: number) => (
<span
key={tag + idx}
@@ -1441,15 +1441,15 @@ export function HostManagerEditor({
}}
className="flex-1 flex flex-col h-full min-h-0"
>
<TabsList>
<TabsTrigger value="password">
<TabsList className="bg-button border border-edge-medium">
<TabsTrigger value="password" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.password")}
</TabsTrigger>
<TabsTrigger value="key">{t("hosts.key")}</TabsTrigger>
<TabsTrigger value="credential">
<TabsTrigger value="key" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">{t("hosts.key")}</TabsTrigger>
<TabsTrigger value="credential" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">
{t("hosts.credential")}
</TabsTrigger>
<TabsTrigger value="none">{t("hosts.none")}</TabsTrigger>
<TabsTrigger value="none" className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium">{t("hosts.none")}</TabsTrigger>
</TabsList>
<TabsContent value="password">
<FormField
@@ -1571,6 +1571,8 @@ export function HostManagerEditor({
EditorView.theme({
".cm-scroller": {
overflow: "auto",
scrollbarWidth: "thin",
scrollbarColor: "var(--scrollbar-thumb) var(--scrollbar-track)",
},
}),
]}
@@ -1609,7 +1611,7 @@ export function HostManagerEditor({
ref={keyTypeButtonRef}
type="button"
variant="outline"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-dark-bg border border-input text-foreground"
className="w-full justify-start text-left rounded-md px-2 py-2 bg-canvas border border-input text-foreground"
onClick={() =>
setKeyTypeDropdownOpen((open) => !open)
}
@@ -1621,7 +1623,7 @@ export function HostManagerEditor({
{keyTypeDropdownOpen && (
<div
ref={keyTypeDropdownRef}
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute bottom-full left-0 z-50 mb-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{keyTypeOptions.map((opt) => (
@@ -1630,7 +1632,7 @@ export function HostManagerEditor({
type="button"
variant="ghost"
size="sm"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-dark-bg text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
className="w-full justify-start text-left rounded-md px-2 py-1.5 bg-canvas text-foreground hover:bg-white/15 focus:bg-white/20 focus:outline-none"
onClick={() => {
field.onChange(opt.value);
setKeyTypeDropdownOpen(false);
@@ -1684,7 +1686,7 @@ export function HostManagerEditor({
control={form.control}
name="overrideCredentialUsername"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.overrideCredentialUsername")}
@@ -1730,7 +1732,7 @@ export function HostManagerEditor({
control={form.control}
name="forceKeyboardInteractive"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.forceKeyboardInteractive")}
@@ -2351,7 +2353,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.cursorBlink"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>{t("hosts.cursorBlink")}</FormLabel>
<FormDescription>
@@ -2444,7 +2446,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.rightClickSelectsWord"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.rightClickSelectsWord")}
@@ -2566,7 +2568,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.agentForwarding"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.sshAgentForwarding")}
@@ -2664,7 +2666,7 @@ export function HostManagerEditor({
<CommandEmpty>
{t("hosts.noSnippetFound")}
</CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-y-auto">
<CommandGroup className="max-h-[300px] overflow-y-auto thin-scrollbar">
<CommandItem
value="none"
onSelect={() => {
@@ -2725,7 +2727,7 @@ export function HostManagerEditor({
control={form.control}
name="terminalConfig.autoMosh"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>Auto-MOSH</FormLabel>
<FormDescription>
@@ -3146,7 +3148,7 @@ export function HostManagerEditor({
index
] = el;
}}
className="absolute top-full left-0 z-50 mt-1 w-full bg-dark-bg border border-input rounded-md shadow-lg max-h-40 overflow-y-auto p-1"
className="absolute top-full left-0 z-50 mt-1 w-full bg-canvas border border-input rounded-md shadow-lg max-h-40 overflow-y-auto thin-scrollbar p-1"
>
<div className="grid grid-cols-1 gap-1 p-0">
{getFilteredSshConfigs(
@@ -3358,7 +3360,7 @@ export function HostManagerEditor({
control={form.control}
name="statsConfig.statusCheckEnabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>
{t("hosts.statusCheckEnabled")}
@@ -3455,7 +3457,7 @@ export function HostManagerEditor({
control={form.control}
name="statsConfig.metricsEnabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 bg-elevated dark:bg-input/30">
<div className="space-y-0.5">
<FormLabel>{t("hosts.metricsEnabled")}</FormLabel>
<FormDescription>