Skip to content

Commit 478ee1f

Browse files
authored
🤖 feat: add Solarized light/dark themes with dropdown selector (#847)
## Summary Adds Solarized Light and Solarized Dark themes, and converts the theme toggle button to a dropdown selector. ### Changes - Extended `ThemeMode` type to include `solarized-light` and `solarized-dark` - Added `THEME_OPTIONS` constant with labels for dropdown - Created `ThemeSelector` dropdown component - Updated GeneralSection settings to use dropdown instead of toggle button - Added complete CSS variable definitions for both Solarized themes using the official palette - Updated command palette to show all theme options dynamically - Fixed syntax highlighting to treat solarized-light as a light theme _Generated with `mux`_
1 parent c0adb8b commit 478ee1f

File tree

9 files changed

+511
-50
lines changed

9 files changed

+511
-50
lines changed

src/browser/components/Messages/MarkdownComponents.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ code, language }) => {
5656

5757
useEffect(() => {
5858
let cancelled = false;
59-
const shikiTheme = themeMode === "light" ? SHIKI_LIGHT_THEME : SHIKI_DARK_THEME;
59+
const isLight = themeMode === "light" || themeMode === "solarized-light";
60+
const shikiTheme = isLight ? SHIKI_LIGHT_THEME : SHIKI_DARK_THEME;
6061

6162
setHighlightedLines(null);
6263

src/browser/components/Settings/sections/GeneralSection.tsx

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React from "react";
2-
import { MoonStar, SunMedium } from "lucide-react";
3-
import { useTheme } from "@/browser/contexts/ThemeContext";
2+
import { useTheme, THEME_OPTIONS, type ThemeMode } from "@/browser/contexts/ThemeContext";
43

54
export function GeneralSection() {
6-
const { theme, toggleTheme } = useTheme();
5+
const { theme, setTheme } = useTheme();
76

87
return (
98
<div className="space-y-6">
@@ -12,25 +11,19 @@ export function GeneralSection() {
1211
<div className="flex items-center justify-between">
1312
<div>
1413
<div className="text-foreground text-sm">Theme</div>
15-
<div className="text-muted text-xs">Choose light or dark appearance</div>
14+
<div className="text-muted text-xs">Choose your preferred theme</div>
1615
</div>
17-
<button
18-
type="button"
19-
onClick={toggleTheme}
20-
className="border-border-medium bg-background-secondary hover:bg-hover flex h-9 items-center gap-2 rounded-md border px-3 text-sm transition-colors"
16+
<select
17+
value={theme}
18+
onChange={(e) => setTheme(e.target.value as ThemeMode)}
19+
className="border-border-medium bg-background-secondary hover:bg-hover h-9 cursor-pointer rounded-md border px-3 text-sm transition-colors focus:outline-none"
2120
>
22-
{theme === "light" ? (
23-
<>
24-
<SunMedium className="h-4 w-4" />
25-
<span>Light</span>
26-
</>
27-
) : (
28-
<>
29-
<MoonStar className="h-4 w-4" />
30-
<span>Dark</span>
31-
</>
32-
)}
33-
</button>
21+
{THEME_OPTIONS.map((option) => (
22+
<option key={option.value} value={option.value}>
23+
{option.label}
24+
</option>
25+
))}
26+
</select>
3427
</div>
3528
</div>
3629
</div>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useTheme, THEME_OPTIONS, type ThemeMode } from "@/browser/contexts/ThemeContext";
2+
import { TooltipWrapper, Tooltip } from "./Tooltip";
3+
4+
export function ThemeSelector() {
5+
const { theme, setTheme } = useTheme();
6+
const currentLabel = THEME_OPTIONS.find((t) => t.value === theme)?.label ?? theme;
7+
8+
return (
9+
<TooltipWrapper>
10+
<select
11+
value={theme}
12+
onChange={(e) => setTheme(e.target.value as ThemeMode)}
13+
className="border-border-light text-muted-foreground hover:border-border-medium/80 hover:bg-toggle-bg/70 focus-visible:ring-border-medium h-5 cursor-pointer appearance-none rounded-md border bg-transparent px-1.5 text-[11px] transition-colors duration-150 focus:outline-none focus-visible:ring-1"
14+
aria-label="Select theme"
15+
data-testid="theme-selector"
16+
>
17+
{THEME_OPTIONS.map((option) => (
18+
<option key={option.value} value={option.value}>
19+
{option.label}
20+
</option>
21+
))}
22+
</select>
23+
<Tooltip align="right">Theme: {currentLabel}</Tooltip>
24+
</TooltipWrapper>
25+
);
26+
}

src/browser/contexts/ThemeContext.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ import React, {
99
import { usePersistedState } from "@/browser/hooks/usePersistedState";
1010
import { UI_THEME_KEY } from "@/common/constants/storage";
1111

12-
export type ThemeMode = "light" | "dark";
12+
export type ThemeMode = "light" | "dark" | "solarized-light" | "solarized-dark";
13+
14+
export const THEME_OPTIONS: Array<{ value: ThemeMode; label: string }> = [
15+
{ value: "light", label: "Light" },
16+
{ value: "dark", label: "Dark" },
17+
{ value: "solarized-light", label: "Solarized Light" },
18+
{ value: "solarized-dark", label: "Solarized Dark" },
19+
];
1320

1421
interface ThemeContextValue {
1522
theme: ThemeMode;
@@ -21,8 +28,17 @@ interface ThemeContextValue {
2128

2229
const ThemeContext = createContext<ThemeContextValue | null>(null);
2330

24-
const DARK_THEME_COLOR = "#1e1e1e";
25-
const LIGHT_THEME_COLOR = "#f5f6f8";
31+
const THEME_COLORS: Record<ThemeMode, string> = {
32+
dark: "#1e1e1e",
33+
light: "#f5f6f8",
34+
"solarized-light": "#fdf6e3",
35+
"solarized-dark": "#002b36",
36+
};
37+
38+
/** Map theme mode to CSS color-scheme value */
39+
function getColorScheme(theme: ThemeMode): "light" | "dark" {
40+
return theme === "light" || theme === "solarized-light" ? "light" : "dark";
41+
}
2642

2743
function resolveSystemTheme(): ThemeMode {
2844
if (typeof window === "undefined" || !window.matchMedia) {
@@ -39,9 +55,9 @@ function applyThemeToDocument(theme: ThemeMode) {
3955

4056
const root = document.documentElement;
4157
root.dataset.theme = theme;
42-
root.style.colorScheme = theme;
58+
root.style.colorScheme = getColorScheme(theme);
4359

44-
const themeColor = theme === "light" ? LIGHT_THEME_COLOR : DARK_THEME_COLOR;
60+
const themeColor = THEME_COLORS[theme];
4561
const meta = document.querySelector<HTMLMetaElement>('meta[name="theme-color"]');
4662
if (meta) {
4763
meta.setAttribute("content", themeColor);
@@ -90,7 +106,12 @@ export function ThemeProvider({
90106

91107
const toggleTheme = useCallback(() => {
92108
if (!isNestedUnderForcedProvider) {
93-
setTheme((current) => (current === "dark" ? "light" : "dark"));
109+
setTheme((current) => {
110+
const themeValues = THEME_OPTIONS.map((t) => t.value);
111+
const currentIndex = themeValues.indexOf(current);
112+
const nextIndex = (currentIndex + 1) % themeValues.length;
113+
return themeValues[nextIndex];
114+
});
94115
}
95116
}, [setTheme, isNestedUnderForcedProvider]);
96117

0 commit comments

Comments
 (0)