Skip to content

Commit ef47c22

Browse files
Add dark/light/auto theme switching (v0.1.3)
Adds a three-way theme toggle to the bottom of the nav sidebar so users can switch between dark, light, and auto (follows OS preference) modes. The choice is persisted in localStorage under programmarr-color-scheme. Changes: - index.html: inline anti-flash script reads localStorage before React loads and sets data-mantine-color-scheme on the html element, so the correct theme is applied immediately with no visible flicker. - App.tsx: swaps hardcoded defaultColorScheme for localStorageColorSchemeManager so the persisted preference is restored on every page load. - theme.ts: replaces hardcoded dark CSS variables in AppShell and Card component overrides with light-dark() so backgrounds adapt correctly in both light and dark mode. - AppLayout.tsx: adds moon/sun/monitor icon buttons at the bottom of the navbar next to the version string. The active mode is filled orange, inactive modes are subtle. Tooltips on hover label each option. - package.json: bump to 0.1.3. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bdc1d4c commit ef47c22

5 files changed

Lines changed: 52 additions & 9 deletions

File tree

frontend/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Programmarr</title>
8+
<script>
9+
(function () {
10+
var stored = localStorage.getItem('programmarr-color-scheme');
11+
var scheme = stored === 'dark' || stored === 'light' ? stored
12+
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
13+
document.documentElement.setAttribute('data-mantine-color-scheme', scheme);
14+
})();
15+
</script>
816
</head>
917
<body>
1018
<div id="root"></div>

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "programmarr",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"private": true,
55
"type": "module",
66
"scripts": {

frontend/src/App.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MantineProvider } from '@mantine/core';
1+
import { localStorageColorSchemeManager, MantineProvider } from '@mantine/core';
22
import { Notifications } from '@mantine/notifications';
33
import { useEffect, useState } from 'react';
44
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
@@ -49,9 +49,11 @@ function AppRoutes() {
4949
);
5050
}
5151

52+
const colorSchemeManager = localStorageColorSchemeManager({ key: 'programmarr-color-scheme' });
53+
5254
export default function App() {
5355
return (
54-
<MantineProvider theme={theme} defaultColorScheme="dark">
56+
<MantineProvider theme={theme} colorSchemeManager={colorSchemeManager} defaultColorScheme="dark">
5557
<Notifications position="top-right" />
5658
<BrowserRouter>
5759
<AppRoutes />

frontend/src/components/AppLayout.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { AppShell, Box, Divider, Group, NavLink, Text } from '@mantine/core';
1+
import { ActionIcon, AppShell, Box, Divider, Group, NavLink, Text, Tooltip } from '@mantine/core';
2+
import { useMantineColorScheme } from '@mantine/core';
23
import { version } from '../../package.json';
34
import {
45
IconBroadcast,
6+
IconDeviceDesktop,
57
IconFileText,
68
IconLayoutDashboard,
79
IconList,
10+
IconMoon,
811
IconPlayerPlay,
912
IconSettings,
13+
IconSun,
1014
} from '@tabler/icons-react';
1115
import { Link, useLocation } from 'react-router-dom';
1216

@@ -20,6 +24,7 @@ const NAV = [
2024

2125
export default function AppLayout({ children }: { children: React.ReactNode }) {
2226
const loc = useLocation();
27+
const { colorScheme, setColorScheme } = useMantineColorScheme();
2328

2429
return (
2530
<AppShell navbar={{ width: 220, breakpoint: 'sm' }} padding="md">
@@ -50,7 +55,35 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
5055
))}
5156

5257
<Box mt="auto" px="sm" pb="sm">
53-
<Text size="xs" c="dimmed">v{version}</Text>
58+
<Group justify="space-between" align="center">
59+
<Text size="xs" c="dimmed">v{version}</Text>
60+
<Group gap={4}>
61+
<Tooltip label="Dark" withArrow position="top" openDelay={400}>
62+
<ActionIcon
63+
size="sm" variant={colorScheme === 'dark' ? 'filled' : 'subtle'}
64+
color="orange" onClick={() => setColorScheme('dark')}
65+
>
66+
<IconMoon size={13} />
67+
</ActionIcon>
68+
</Tooltip>
69+
<Tooltip label="Light" withArrow position="top" openDelay={400}>
70+
<ActionIcon
71+
size="sm" variant={colorScheme === 'light' ? 'filled' : 'subtle'}
72+
color="orange" onClick={() => setColorScheme('light')}
73+
>
74+
<IconSun size={13} />
75+
</ActionIcon>
76+
</Tooltip>
77+
<Tooltip label="Auto" withArrow position="top" openDelay={400}>
78+
<ActionIcon
79+
size="sm" variant={colorScheme === 'auto' ? 'filled' : 'subtle'}
80+
color="orange" onClick={() => setColorScheme('auto')}
81+
>
82+
<IconDeviceDesktop size={13} />
83+
</ActionIcon>
84+
</Tooltip>
85+
</Group>
86+
</Group>
5487
</Box>
5588
</AppShell.Navbar>
5689

frontend/src/theme.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ export const theme = createTheme({
1414
AppShell: {
1515
styles: {
1616
navbar: {
17-
backgroundColor: 'var(--mantine-color-dark-7)',
18-
borderRight: '1px solid var(--mantine-color-dark-5)',
17+
backgroundColor: 'light-dark(var(--mantine-color-white), var(--mantine-color-dark-7))',
18+
borderRight: '1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-5))',
1919
},
2020
main: {
21-
backgroundColor: 'var(--mantine-color-dark-8)',
21+
backgroundColor: 'light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8))',
2222
},
2323
},
2424
},
2525
Card: {
2626
defaultProps: { withBorder: true },
27-
styles: { root: { backgroundColor: 'var(--mantine-color-dark-6)' } },
27+
styles: { root: { backgroundColor: 'light-dark(var(--mantine-color-white), var(--mantine-color-dark-6))' } },
2828
},
2929
},
3030
});

0 commit comments

Comments
 (0)