Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ declare global {
deepLinks?: string[]
}
api?: {
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise<void>
exportDebugLogs?: () => Promise<string>
}
}
Expand Down Expand Up @@ -318,8 +318,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
<MetaProvider>
<Font />
<ThemeProvider
onThemeApplied={(_, mode) => {
void window.api?.setTitlebar?.({ mode })
onThemeApplied={(_, mode, scheme) => {
void window.api?.setTitlebar?.({ mode, scheme })
}}
>
<LanguageProvider locale={props.locale}>
Expand Down
14 changes: 12 additions & 2 deletions packages/desktop/src/main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ export function setAppQuitting(quitting = true) {

export function setBackgroundColor(color: string) {
backgroundColor = color
BrowserWindow.getAllWindows().forEach((win) => win.setBackgroundColor(color))
BrowserWindow.getAllWindows().forEach((win) => {
win.setBackgroundColor(color)
if (process.platform === "darwin") win.invalidateShadow()
})
}

export function getBackgroundColor(): string | undefined {
Expand Down Expand Up @@ -106,6 +109,13 @@ function overlay(theme: Partial<TitlebarTheme> = {}, zoom = 1) {

export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> = {}) {
titlebarThemes.set(win, theme)
// macOS draws the window frame hairline and shadow using the NSWindow
// appearance, which follows nativeTheme rather than the rendered content.
// Align it with the app theme so a light app on a dark system does not get
// the dark-appearance border and shadow. A "system" scheme must map to
// "system" (not the resolved mode) or prefers-color-scheme stops tracking
// OS appearance changes in the renderer.
if (process.platform === "darwin") nativeTheme.themeSource = theme.scheme ?? theme.mode ?? "system"
updateTitlebar(win)
}

Expand Down Expand Up @@ -172,7 +182,7 @@ export function createMainWindow(id: string = randomUUID()) {
...(process.platform === "darwin"
? {
titleBarStyle: "hidden" as const,
trafficLightPosition: { x: 12, y: 14 },
trafficLightPosition: { x: 14, y: 14 },
}
: {}),
...(process.platform === "win32"
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/src/preload/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type UpdaterAPI = {
export type LinuxDisplayBackend = "wayland" | "auto"
export type TitlebarTheme = {
mode: "light" | "dark"
scheme?: "system" | "light" | "dark"
}
export type FatalRendererError = {
error: string
Expand Down
17 changes: 10 additions & 7 deletions packages/ui/src/theme/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ function cacheThemeVariants(theme: DesktopTheme, themeId: string) {

export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
name: "Theme",
init: (props: { defaultTheme?: string; onThemeApplied?: (theme: DesktopTheme, mode: "light" | "dark") => void }) => {
init: (props: {
defaultTheme?: string
onThemeApplied?: (theme: DesktopTheme, mode: "light" | "dark", scheme: ColorScheme) => void
}) => {
const themeId = normalize(read(STORAGE_KEYS.THEME_ID) ?? props.defaultTheme) ?? "oc-2"
const colorScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system"
const mode = colorScheme === "system" ? getSystemMode() : colorScheme
Expand Down Expand Up @@ -212,9 +215,9 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
return task
}

const applyTheme = (theme: DesktopTheme, themeId: string, mode: "light" | "dark") => {
const applyTheme = (theme: DesktopTheme, themeId: string, mode: "light" | "dark", scheme: ColorScheme) => {
applyThemeCss(theme, themeId, mode)
props.onThemeApplied?.(theme, mode)
props.onThemeApplied?.(theme, mode, scheme)
}

const ids = () => {
Expand Down Expand Up @@ -278,7 +281,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
createEffect(() => {
const theme = store.themes[store.themeId]
if (!theme) return
applyTheme(theme, store.themeId, store.mode)
applyTheme(theme, store.themeId, store.mode, store.colorScheme)
})

const setTheme = (id: string) => {
Expand Down Expand Up @@ -333,7 +336,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
? getSystemMode()
: store.previewScheme
: store.mode
applyTheme(theme, next, mode)
applyTheme(theme, next, mode, store.previewScheme ?? store.colorScheme)
})
},
previewColorScheme: (scheme: ColorScheme) => {
Expand All @@ -344,7 +347,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
if (!theme) return
if ((store.previewThemeId ?? store.themeId) !== id) return
if (store.previewScheme !== scheme) return
applyTheme(theme, id, mode)
applyTheme(theme, id, mode, scheme)
})
},
commitPreview: () => {
Expand All @@ -362,7 +365,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
setStore("previewScheme", null)
void load(store.themeId).then((theme) => {
if (!theme) return
applyTheme(theme, store.themeId, store.mode)
applyTheme(theme, store.themeId, store.mode, store.colorScheme)
})
},
}
Expand Down
Loading