Skip to content

Commit bd4746e

Browse files
committed
Add automatic background change when toggling between light & dark
1 parent a82b700 commit bd4746e

8 files changed

Lines changed: 48 additions & 14 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
"devDependencies": {
2626
"@eslint/js": "^9.39.1",
27-
"@types/lodash": "^4.17.23",
27+
"@types/lodash": "^4.17.24",
2828
"@types/node": "^24.10.13",
2929
"@types/react": "^19.2.14",
3030
"@types/react-dom": "^19.2.3",

src/components/FieldGroupContainer/FieldGroupContainer.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Box, Typography } from "@mui/material";
22
import type { PropsWithChildren, ReactNode } from "react";
3-
import { DIVIDER_COLOR } from "../../constants";
43

54
export interface FieldGroupContainerProps {
65
title?: string;
@@ -15,7 +14,7 @@ export const FieldGroupContainer = ({ title, actions, children }: PropsWithChild
1514
sx={{
1615
p: 2,
1716
borderBottom: 1,
18-
borderColor: DIVIDER_COLOR,
17+
borderColor: "divider",
1918
}}
2019
>
2120
{hasHeader && (

src/components/PaletteEditor/PaletteEditor.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,44 @@
1-
import { ToggleButton, ToggleButtonGroup, type PaletteOptions } from "@mui/material";
1+
import { getLuminance, lighten, ToggleButton, ToggleButtonGroup, type PaletteOptions } from "@mui/material";
2+
import type { ThemeOptions } from "@mui/material/styles";
3+
import { DEFAULT_DARK_THEME, DEFAULT_THEME } from "../../constants";
24
import { useInnerTheme } from "../../hooks/useInnerTheme";
5+
import { invertLightness } from "../../utils";
36
import { FieldGroupContainer } from "../FieldGroupContainer/FieldGroupContainer";
47
import { SubpaletteEditor } from "../SubpaletteEditor/SubpaletteEditor";
58

69
const COLORS_BOTTOM: Array<keyof PaletteOptions> = ["secondary", "error", "warning", "info", "success"];
10+
const defaultDarkBackground = DEFAULT_DARK_THEME.palette.background.default;
11+
const defaultLightBackground = DEFAULT_THEME.palette.background.default;
712

813
export const PaletteEditor = () => {
914
const { theme, mergeThemeOptions } = useInnerTheme();
15+
const currentMode = theme.palette.mode;
16+
const isLight = currentMode === "light";
17+
const { default: defaultBg } = theme.palette.background;
1018
return (
1119
<>
1220
<FieldGroupContainer>
1321
<ToggleButtonGroup
1422
fullWidth
15-
value={theme.palette.mode}
23+
value={currentMode}
1624
exclusive
1725
onChange={(_, value: "light" | "dark" | null) => {
1826
if (!value) return;
19-
mergeThemeOptions({ palette: { mode: value } });
27+
const newTheme: ThemeOptions = { palette: { mode: value } };
28+
const isNotDefaultBg = defaultBg !== (isLight ? defaultLightBackground : defaultDarkBackground);
29+
const isDarkAndUsingDarkBg = !isLight && getLuminance(defaultBg) < 0.3;
30+
const isLightAndUsingLightBg = isLight && getLuminance(defaultBg) > 0.3;
31+
32+
// Automatically adjust the background if switching modes
33+
if (isNotDefaultBg && (isLightAndUsingLightBg || isDarkAndUsingDarkBg)) {
34+
const newBackground = invertLightness(defaultBg);
35+
newTheme!.palette!.background = {
36+
paper: isLight ? newBackground : lighten(newBackground, Math.min(1, 1.3 - getLuminance(newBackground))),
37+
default: newBackground,
38+
};
39+
}
40+
41+
mergeThemeOptions(newTheme);
2042
}}
2143
>
2244
<ToggleButton value="light">Light</ToggleButton>

src/components/SubpaletteEditor/SubpaletteEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const SubpaletteEditor = ({ name, manual = [], derived = [] }: Subpalette
5555
name={name + key}
5656
title={key}
5757
key={key}
58-
isDefault={theme.palette[name][key] === PALETTES[mode][name][key]}
58+
isDefault={theme.palette[name][key] === toStandardHex(PALETTES[mode][name][key])}
5959
value={toStandardHex(theme.palette[name][key])}
6060
onChange={(hex) =>
6161
mergeThemeOptions({

src/components/ToolsPanel/ToolsPanel.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Code, Edit } from "@mui/icons-material";
22
import { Paper, Tab, Tabs } from "@mui/material";
33
import { Activity, useState } from "react";
4-
import { DIVIDER_COLOR } from "../../constants";
54
import { CodePanel } from "../CodePanel/CodePanel";
65
import { EditorPanel } from "../EditorPanel/EditorPanel";
76

@@ -16,7 +15,7 @@ export const ToolsPanel = () => {
1615
flexDirection: "column",
1716
}}
1817
>
19-
<Tabs variant="fullWidth" onChange={(_, value) => setTab(value)} value={tab} sx={{ borderBottom: 1, borderColor: DIVIDER_COLOR }}>
18+
<Tabs variant="fullWidth" onChange={(_, value) => setTab(value)} value={tab} sx={{ borderBottom: 1, borderColor: "divider" }}>
2019
<Tab icon={<Edit />} iconPosition="start" sx={{ minHeight: "48px" }} label="Editor" value="editor" />
2120
<Tab icon={<Code />} iconPosition="start" sx={{ minHeight: "48px" }} label="Code" value="code" />
2221
</Tabs>

src/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { createTheme } from "@mui/material";
22

3-
export const DIVIDER_COLOR = "grey.800";
43
export const DEFAULT_THEME = createTheme({});
54
export const DEFAULT_DARK_THEME = createTheme({
65
palette: { mode: "dark" },

src/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,18 @@ export const generateEmptyShadows = () => {
100100
shadows.unshift("none");
101101
return shadows as Shadows;
102102
};
103+
104+
// Generated by Claude
105+
export function invertLightness(hex: string): string {
106+
const r = parseInt(hex.slice(1, 3), 16);
107+
const g = parseInt(hex.slice(3, 5), 16);
108+
const b = parseInt(hex.slice(5, 7), 16);
109+
110+
// HSL lightness = (max + min) / 2, so its inverse has (max + min) = 510 - original.
111+
// Shifting all channels by (255 - (max + min)) achieves this exactly in integer arithmetic.
112+
const shift = 255 - (Math.max(r, g, b) + Math.min(r, g, b));
113+
114+
const clamp = (n: number) => Math.min(255, Math.max(0, n));
115+
116+
return "#" + intToHex(clamp(r + shift)) + intToHex(clamp(g + shift)) + intToHex(clamp(b + shift));
117+
}

0 commit comments

Comments
 (0)