Skip to content

Commit 73c1cb7

Browse files
committed
Implement theme provider and toggle
1 parent 60f7935 commit 73c1cb7

File tree

6 files changed

+63
-2
lines changed

6 files changed

+63
-2
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
desktop: minor
3+
---
4+
5+
Add dark mode

apps/desktop/src/main/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Command, FetchHttpClient, Path, Url } from '@effect/platform';
22
import * as NodeContext from '@effect/platform-node/NodeContext';
33
import * as NodeRuntime from '@effect/platform-node/NodeRuntime';
44
import { Config, Console, Effect, pipe, Runtime, String } from 'effect';
5-
import { app, BrowserWindow, dialog, Dialog, globalShortcut, ipcMain, protocol, shell } from 'electron';
5+
import { app, BrowserWindow, dialog, Dialog, globalShortcut, ipcMain, nativeTheme, protocol, shell } from 'electron';
66
import { autoUpdater } from 'electron-updater';
77
import os from 'node:os';
88
import { Agent } from 'undici';
@@ -31,6 +31,7 @@ const createWindow = Effect.gen(function* () {
3131

3232
// Create the browser window.
3333
const mainWindow = new BrowserWindow({
34+
backgroundColor: nativeTheme.shouldUseDarkColors ? '#18181b' : 'white',
3435
height: 600,
3536
icon,
3637
title: 'DevTools Studio',

apps/desktop/src/renderer/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import { Button } from '@the-dev-tools/ui/button';
99
import { Logo } from '@the-dev-tools/ui/illustrations';
1010
import { ProgressBar } from '@the-dev-tools/ui/progress-bar';
1111
import { tw } from '@the-dev-tools/ui/tailwind-literal';
12+
import { setTheme } from '@the-dev-tools/ui/theme';
1213
import packageJson from '../../package.json';
1314

1415
import './styles.css';
1516

17+
setTheme();
18+
1619
pipe(configProviderFromMetaEnv({ VERSION: packageJson.version }), Layer.setConfigProvider, addGlobalLayer);
1720

1821
const updateCheckAtom = runtimeAtom.atom(

packages/client/src/shared/ui/dashboard.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Outlet, useRouter } from '@tanstack/react-router';
22
import { Suspense } from 'react';
3-
import { ButtonAsRouteLink } from '@the-dev-tools/ui/button';
3+
import { FiMoon, FiSun } from 'react-icons/fi';
4+
import { Button, ButtonAsRouteLink } from '@the-dev-tools/ui/button';
45
import { Logo } from '@the-dev-tools/ui/illustrations';
56
import { Spinner } from '@the-dev-tools/ui/spinner';
67
import { tw } from '@the-dev-tools/ui/tailwind-literal';
8+
import { useTheme } from '@the-dev-tools/ui/theme';
79
import { routes } from '../routes';
810

911
interface DashboardLayoutProps {
@@ -14,6 +16,8 @@ interface DashboardLayoutProps {
1416
export const DashboardLayout = ({ children, navbar }: DashboardLayoutProps) => {
1517
const router = useRouter();
1618

19+
const { theme, toggleTheme } = useTheme();
20+
1721
return (
1822
<div className={tw`flex h-full flex-col`}>
1923
<div
@@ -34,6 +38,13 @@ export const DashboardLayout = ({ children, navbar }: DashboardLayoutProps) => {
3438

3539
{navbar}
3640

41+
<Button className={tw`p-1 text-xl`} onPress={() => void toggleTheme()} variant='ghost dark'>
42+
{theme === 'light' && <FiSun />}
43+
{theme === 'dark' && <FiMoon />}
44+
</Button>
45+
46+
<div className={tw`-ml-1.5 h-5 w-px bg-on-inverse-lower`} />
47+
3748
<a href='https://github.com/the-dev-tools/dev-tools' rel='noreferrer' target='_blank'>
3849
<img alt='GitHub Repo stars' src='https://img.shields.io/github/stars/the-dev-tools/dev-tools' />
3950
</a>

packages/ui/src/provider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Option } from 'effect';
22
import { ReactNode } from 'react';
33
import * as RAC from 'react-aria-components';
4+
import { ThemeProvider } from './theme';
45
import { ToastQueue, ToastQueueContext } from './toast';
56

67
export interface UiProviderProps {
@@ -11,5 +12,6 @@ export interface UiProviderProps {
1112
export const UiProvider = ({ children, toastQueue }: UiProviderProps) => {
1213
let _ = <RAC.RouterProvider navigate={() => undefined}>{children}</RAC.RouterProvider>;
1314
_ = <ToastQueueContext.Provider value={Option.fromNullable(toastQueue)}>{_}</ToastQueueContext.Provider>;
15+
_ = <ThemeProvider>{_}</ThemeProvider>;
1416
return _;
1517
};

packages/ui/src/theme.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Option, pipe } from 'effect';
2+
import { createContext, PropsWithChildren, use, useState } from 'react';
3+
4+
type Theme = 'dark' | 'light';
5+
6+
const getStoreTheme = () => localStorage.getItem('theme') as Theme | undefined;
7+
const getSystemTheme = () => (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
8+
9+
export const setTheme = (theme?: Theme) => {
10+
const systemTheme = getSystemTheme();
11+
const storeTheme = theme ?? getStoreTheme() ?? systemTheme;
12+
13+
if (storeTheme === systemTheme) localStorage.removeItem('theme');
14+
else localStorage.setItem('theme', storeTheme);
15+
16+
if (storeTheme === 'dark') document.documentElement.classList.add('dark');
17+
else document.documentElement.classList.remove('dark');
18+
};
19+
20+
interface ThemeContext {
21+
theme: Theme;
22+
toggleTheme: () => void;
23+
}
24+
25+
const ThemeContext = createContext(Option.none<ThemeContext>());
26+
27+
export const useTheme = () => pipe(use(ThemeContext), Option.getOrThrow);
28+
29+
export const ThemeProvider = ({ children }: PropsWithChildren) => {
30+
const [theme, setThemeState] = useState(getStoreTheme() ?? getSystemTheme());
31+
32+
const toggleTheme = () => {
33+
const nextTheme = theme === 'dark' ? 'light' : 'dark';
34+
setTheme(nextTheme);
35+
setThemeState(nextTheme);
36+
};
37+
38+
return <ThemeContext.Provider value={Option.some({ theme, toggleTheme })}>{children}</ThemeContext.Provider>;
39+
};

0 commit comments

Comments
 (0)