feat: add light mode for navigation sidebar (#8057)

* feat: add light mode support for navigation sidebar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: use custom selected background for sidebar items

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: darken sidebar section separators in light mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: lighten sidebar bg to #F3F3F7 and use border-light for separators

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: use text-secondary for sidebar labels and text-hint for icons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract dark sidebar background color to constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Guilhem
2026-02-23 15:25:46 +00:00
committed by GitHub
parent ba0d777248
commit bbb8bb5d90
6 changed files with 47 additions and 36 deletions
@@ -20,8 +20,8 @@
<button
class={twMerge(
'text-2xs text-white m-1 p-2 rounded-lg flex flex-row gap-2 justify-center hover:bg-gray-600',
forcedDarkMode ? 'text-white hover:bg-gray-600' : 'text-primary hover:bg-surface-hover'
'text-2xs m-1 p-2 rounded-lg flex flex-row gap-2 justify-center',
forcedDarkMode ? 'text-white hover:bg-gray-600' : 'text-secondary hover:bg-surface-hover'
)}
on:click={toggle}
>
@@ -1,10 +1,11 @@
<script module lang="ts">
export const sidebarClasses = {
text: 'text-primary-inverse dark:text-primary data-[light-mode=true]:text-primary text-xs font-normal',
selectedText: 'text-emphasis-inverse dark:text-emphasis text-xs font-normal',
sublabelText: 'text-secondary-inverse dark:text-secondary text-2xs font-normal',
hoverBg:
'transition-colors hover:bg-surface-hover-inverse dark:hover:bg-surface-hover data-[light-mode=true]:hover:bg-surface-hover'
text: 'text-secondary text-xs font-normal',
iconText: 'text-hint',
selectedText: 'text-emphasis text-xs font-semibold',
sublabelText: 'text-secondary text-2xs font-normal',
hoverBg: 'transition-colors hover:bg-surface-hover',
selectedBg: 'bg-surface-hover'
}
</script>
@@ -113,7 +114,7 @@
<foreignObject x="5" y="5" width="16" height="16">
<SvelteComponent
size={16}
class={twMerge(sidebarClasses.text, 'transition-colors', iconClasses)}
class={twMerge(sidebarClasses.iconText, 'transition-colors', iconClasses)}
{...iconProps}
/>
</foreignObject>
@@ -121,7 +122,7 @@
{:else}
<SvelteComponent
size={16}
class={twMerge('flex-shrink-0', sidebarClasses.text, 'transition-colors', iconClasses)}
class={twMerge('flex-shrink-0', sidebarClasses.iconText, 'transition-colors', iconClasses)}
{...iconProps}
/>
{/if}
@@ -140,7 +141,7 @@
>
{label}
<span
class="pl-2 text-xs dark:text-secondary light:text-secondary-inverse font-semibold"
class="pl-2 text-xs text-secondary font-semibold"
>
{shortcut}
</span>
@@ -61,9 +61,9 @@
}}
{onclick}
class={twMerge(
'group flex items-center px-2 py-2 text-sm font-light rounded-md h-8 gap-3',
'group flex items-center px-2 py-2 text-sm font-light rounded-md h-8 gap-2',
isSelected
? 'bg-surface-selected-inverse dark:bg-surface-selected'
? sidebarClasses.selectedBg
: sidebarClasses.hoverBg,
isSelected ? sidebarClasses.selectedText : sidebarClasses.text,
classNames
@@ -77,7 +77,7 @@
>
{#if icon}
{@const SvelteComponent = icon}
<SvelteComponent size={16} class={twMerge('flex-shrink-0 transition-all')} />
<SvelteComponent size={16} class={twMerge('flex-shrink-0 transition-all', isSelected ? sidebarClasses.selectedText : sidebarClasses.iconText)} />
{/if}
{#if !isCollapsed}
@@ -551,7 +551,7 @@
</div>
<div class="pt-4">
<div
class="text-gray-400 text-[0.5rem] uppercase transition-opacity"
class="text-secondary text-[0.5rem] uppercase transition-opacity"
class:opacity-0={isCollapsed}>Triggers</div
>
<Menubar class="flex flex-col gap-1">
@@ -566,8 +566,8 @@
aiId="sidebar-menu-link-add-trigger"
aiDescription="Button to add a new trigger. Can be HTTP, WebSocket, Postgres, Kafka, NATS, SQS, GCP Pub/Sub, or MQTT"
class={twMerge(
'w-full text-gray-400 text-2xs flex flex-row gap-1 py-1 items-center px-2 hover:bg-[#2A3648] dark:hover:bg-[#30404e] rounded',
'data-[highlighted]:bg-[#2A3648] dark:data-[highlighted]:bg-[#30404e]'
'w-full text-secondary text-2xs flex flex-row gap-1 py-1 items-center px-2 hover:bg-surface-hover rounded',
'data-[highlighted]:bg-surface-hover'
)}
meltElement={trigger}
>
@@ -158,7 +158,7 @@
<div
class={twMerge(
'font-mono text-2xs whitespace-nowrap truncate text-left font-normal',
isSelected ? 'text-accent/80' : 'text-hint'
isSelected ? 'text-accent/80' : 'text-secondary'
)}
title={workspace.id}
>
@@ -67,6 +67,7 @@
import { DEFAULT_HUB_BASE_URL } from '$lib/hub'
import DBManagerDrawer from '$lib/components/DBManagerDrawer.svelte'
import { watchOnce } from 'runed'
import { useIsDarkMode } from '$lib/components/DarkModeObserver.svelte'
interface Props {
children?: import('svelte').Snippet
}
@@ -85,6 +86,11 @@
let userSettings: UserSettings | undefined = $state()
let superadminSettings: SuperadminSettings | undefined = $state()
let menuHidden = $state(false)
let isDarkMode = useIsDarkMode()
let darkMode = $derived(isDarkMode.val)
const SIDEBAR_BG = '#F3F3F7'
const SIDEBAR_BG_DARK = '#1e232e'
if ($page.status == 404) {
goto('/user/login')
@@ -475,7 +481,7 @@
>
<div
class={classNames(
'fixed inset-0 dark:bg-[#1e232e] bg-[#202125] dark:bg-opacity-75 bg-opacity-75 transition-opacity ease-linear duration-300 z-40 !dark',
'fixed inset-0 bg-black/50 transition-opacity ease-linear duration-300 z-40',
menuOpen ? 'opacity-100' : 'opacity-0'
)}
@@ -519,16 +525,19 @@
</svg>
</button>
</div>
<div class="dark:bg-[#1e232e] bg-[#202125] h-full !dark flex flex-col">
<div class="flex gap-x-2 flex-shrink-0 p-4 font-semibold text-gray-200 w-40">
<WindmillIcon white={true} height="20px" width="20px" />
<div
class="h-full flex flex-col"
style:background-color={darkMode ? SIDEBAR_BG_DARK : SIDEBAR_BG}
>
<div class="flex gap-x-2 flex-shrink-0 p-4 font-semibold text-emphasis w-40">
<WindmillIcon white={darkMode} height="20px" width="20px" />
{#if $whitelabelNameStore}
{$whitelabelNameStore}
{:else}
Windmill
{/if}
</div>
<div class="px-2 py-4 border-y border-gray-500">
<div class="px-2 py-4 border-y border-light dark:border-gray-700">
<Menubar>
{#snippet children({ createMenu })}
<WorkspaceMenu {createMenu} />
@@ -554,7 +563,7 @@
}}
label="Ask AI"
class="!text-xs"
iconClasses="!text-ai-inverse dark:!text-ai"
iconClasses="!text-ai"
shortcut={`${getModifierKey()}L`}
/>
</div>
@@ -573,13 +582,14 @@
<div
id="sidebar"
class={classNames(
'flex flex-col fixed inset-y-0 transition-all ease-in-out duration-200 shadow-md z-40 ',
'flex flex-col fixed inset-y-0 transition-all ease-in-out duration-200 z-40 ',
isCollapsed ? 'md:w-12' : 'md:w-40',
devOnly ? '!hidden' : ''
)}
>
<div
class="flex-1 flex flex-col min-h-0 h-screen shadow-lg dark:bg-[#1e232e] bg-[#202125] !dark"
class="flex-1 flex flex-col min-h-0 h-screen border-r border-light dark:border-gray-700"
style:background-color={darkMode ? SIDEBAR_BG_DARK : SIDEBAR_BG}
>
<button
onclick={() => {
@@ -591,10 +601,10 @@
class:w-40={!isCollapsed}
>
<div class:mr-1={!isCollapsed}>
<WindmillIcon white={true} height="20px" width="20px" />
<WindmillIcon white={darkMode} height="20px" width="20px" />
</div>
{#if !isCollapsed}
<div class="text-sm mt-0.5 text-white">
<div class="text-sm mt-0.5 text-emphasis">
{#if $whitelabelNameStore}{capitalize(
$whitelabelNameStore
)}{:else}Windmill{/if}
@@ -602,7 +612,7 @@
{/if}
</div>
</button>
<div class="px-2 py-4 border-y border-gray-700 flex flex-col gap-1">
<div class="px-2 py-4 border-y border-light dark:border-gray-700 flex flex-col gap-1">
<Menubar class="flex flex-col gap-1">
{#snippet children({ createMenu })}
<WorkspaceMenu {createMenu} {isCollapsed} />
@@ -632,7 +642,7 @@
}}
label="Ask AI"
class="!text-xs"
iconClasses="!text-ai-inverse dark:!text-ai"
iconClasses="!text-ai"
shortcut={`${getModifierKey()}L`}
/>
</div>
@@ -653,7 +663,7 @@
<ArrowLeft
size={16}
class={classNames(
'flex-shrink-0 h-4 w-4 transition-all ease-in-out duration-200 text-white',
'flex-shrink-0 h-4 w-4 transition-all ease-in-out duration-200 text-secondary',
isCollapsed ? 'rotate-180' : 'rotate-0'
)}
/>
@@ -670,7 +680,7 @@
<!-- Legacy menu -->
<div
class={classNames(
'fixed inset-0 dark:bg-[#1e232e] bg-[#202125] dark:bg-opacity-75 bg-opacity-75 transition-opacity ease-linear duration-300 !dark',
'fixed inset-0 bg-black/50 transition-opacity ease-linear duration-300',
'opacity-0 pointer-events-none'
)}
>
@@ -708,18 +718,18 @@
</svg>
</button>
</div>
<div class="dark:bg-[#1e232e] bg-[#202125] h-full !dark">
<div class="h-full" style:background-color={darkMode ? SIDEBAR_BG_DARK : SIDEBAR_BG}>
<div
class="flex gap-x-2 flex-shrink-0 p-4 font-semibold text-gray-200 w-10"
class="flex gap-x-2 flex-shrink-0 p-4 font-semibold text-emphasis w-10"
class:w-40={!isCollapsed}
>
<WindmillIcon white={true} height="20px" width="20px" />
<WindmillIcon white={darkMode} height="20px" width="20px" />
{#if !isCollapsed}{#if $whitelabelNameStore}{capitalize(
$whitelabelNameStore
)}{:else}Windmill{/if}{/if}
</div>
<div class="px-2 py-4 space-y-2 border-y border-gray-500">
<div class="px-2 py-4 space-y-2 border-y border-light dark:border-gray-700">
<Menubar>
{#snippet children({ createMenu })}
<WorkspaceMenu {createMenu} />
@@ -745,7 +755,7 @@
}}
label="Ask AI"
class="!text-xs"
iconClasses="!text-ai-inverse dark:!text-ai"
iconClasses="!text-ai"
shortcut={`${getModifierKey()}L`}
/>
</div>