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 bun_client/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Modal,
Select,
Theme,
VALID_THEMES,
resolveTheme,
THEME_OPTIONS,
ReviewLocation,
REVIEW_LOCATION_OPTIONS,
Expand All @@ -34,8 +34,8 @@ function App() {
const [navigating, setNavigating] = useState(false);
const isMobile = useIsMobile();
const [theme, setTheme] = useState<Theme>(() => {
const saved = localStorage.getItem('theme') as Theme;
if (saved && VALID_THEMES.includes(saved)) return saved;
const saved = resolveTheme(localStorage.getItem('theme'));
if (saved) return saved;
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
});
const [reviewLocation, setReviewLocation] = useState<ReviewLocation>(() => {
Expand Down
35 changes: 2 additions & 33 deletions bun_client/frontend/src/components/CodeViewerModal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import {
oneDark,
oneLight,
gruvboxDark,
gruvboxLight,
solarizedlight,
solarizedDarkAtom,
dracula,
nord,
nightOwl,
} from 'react-syntax-highlighter/dist/esm/styles/prism';
import { colors, shadows } from '../design';
import { getSyntaxTheme } from './review/diff_theme';
import { readFile, listFiles } from '../api';
import type { Theme } from '../design';
import { useLsp } from '../hooks/useLsp';
Expand Down Expand Up @@ -472,28 +462,7 @@ export default function CodeViewerModal({
};

// Get theme for syntax highlighting
const syntaxTheme = useMemo(() => {
switch (theme) {
case 'light':
return oneLight;
case 'gruvbox-dark':
return gruvboxDark;
case 'gruvbox-light':
return gruvboxLight;
case 'solarized-light':
return solarizedlight;
case 'solarized-dark':
return solarizedDarkAtom;
case 'dracula':
return dracula;
case 'nord':
return nord;
case 'night-owl':
return nightOwl;
default:
return oneDark;
}
}, [theme]);
const syntaxTheme = useMemo(() => getSyntaxTheme(theme), [theme]);

const toggleDir = useCallback(
(path: string) => {
Expand Down
64 changes: 41 additions & 23 deletions bun_client/frontend/src/components/review/diff_theme.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,60 @@
import {
oneDark,
oneLight,
ghcolors,
vscDarkPlus,
gruvboxDark,
gruvboxLight,
solarizedlight,
solarizedDarkAtom,
okaidia,
dracula,
nord,
nightOwl,
synthwave84,
} from 'react-syntax-highlighter/dist/esm/styles/prism';
import type { Theme } from '../../design';

// Prism style that pairs with an app theme. Themes with no close Prism
// equivalent fall back to One Dark (or One Light for the light flavors) so the
// syntax colors never fight the surrounding chrome.
export const getSyntaxTheme = (theme: Theme) => {
switch (theme) {
case 'light':
return oneLight;
case 'github-light':
return ghcolors;
case 'github-dark':
return vscDarkPlus;
case 'gruvbox-dark':
return gruvboxDark;
case 'gruvbox-light':
return gruvboxLight;
case 'solarized-light':
return solarizedlight;
case 'solarized-dark':
return solarizedDarkAtom;
case 'monokai':
return okaidia;
case 'dracula':
return dracula;
case 'nord':
return nord;
case 'night-owl':
return nightOwl;
case 'synthwave-84':
return synthwave84;
case 'catppuccin-latte':
return oneLight;
default:
return oneDark;
}
};

// Custom Prism theme based on the selected app theme, adjusted so highlighted
// lines blend into the diff rows (transparent background, no padding).
export const buildDiffTheme = (theme: Theme) => {
const getBaseTheme = () => {
switch (theme) {
case 'light':
return oneLight;
case 'gruvbox-dark':
return gruvboxDark;
case 'gruvbox-light':
return gruvboxLight;
case 'solarized-light':
return solarizedlight;
case 'solarized-dark':
return solarizedDarkAtom;
case 'dracula':
return dracula;
case 'nord':
return nord;
case 'night-owl':
return nightOwl;
default:
return oneDark;
}
};
const baseTheme = getBaseTheme();
const baseTheme = getSyntaxTheme(theme);
return {
...baseTheme,
'pre[class*="language-"]': {
Expand Down
39 changes: 37 additions & 2 deletions bun_client/frontend/src/design/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
export const VALID_THEMES = [
'light',
'dark',
'github-light',
'github-dark',
'gruvbox-dark',
'gruvbox-light',
'solarized-light',
'solarized-dark',
'monokai',
'dracula',
'nord',
'night-owl',
'tokyo-night',
'catppuccin',
'catppuccin-latte',
'catppuccin-frappe',
'catppuccin-macchiato',
'catppuccin-mocha',
'everforest',
'rose-pine',
'synthwave-84',
Expand All @@ -29,20 +35,49 @@ export interface ThemeOption {
export const THEME_OPTIONS: ThemeOption[] = [
{ value: 'dark', label: '🌙 Dark (One Dark)' },
{ value: 'light', label: '☀️ Light (One Light)' },
{ value: 'github-dark', label: '🐙 GitHub Dark' },
{ value: 'github-light', label: '🐙 GitHub Light' },
{ value: 'gruvbox-dark', label: '📦 Gruvbox Dark' },
{ value: 'gruvbox-light', label: '📦 Gruvbox Light' },
{ value: 'solarized-dark', label: '☀️ Solarized Dark' },
{ value: 'solarized-light', label: '☀️ Solarized Light' },
{ value: 'monokai', label: '🍬 Monokai' },
{ value: 'dracula', label: '🧛 Dracula' },
{ value: 'nord', label: '❄️ Nord' },
{ value: 'night-owl', label: '🦉 Night Owl' },
{ value: 'tokyo-night', label: '🌃 Tokyo Night' },
{ value: 'catppuccin', label: '🐱 Catppuccin' },
{ value: 'catppuccin-mocha', label: '🐱 Catppuccin Mocha' },
{ value: 'catppuccin-macchiato', label: '🐱 Catppuccin Macchiato' },
{ value: 'catppuccin-frappe', label: '🐱 Catppuccin Frappé' },
{ value: 'catppuccin-latte', label: '🐱 Catppuccin Latte' },
{ value: 'everforest', label: '🌲 Everforest' },
{ value: 'rose-pine', label: '🌹 Rose Pine' },
{ value: 'synthwave-84', label: "🕹️ SynthWave '84" },
];

/**
* Themes that have been renamed. A value persisted by an older client maps to
* its current equivalent instead of silently falling back to the default.
*/
const LEGACY_THEME_ALIASES: Record<string, Theme> = {
// 'catppuccin' shipped as Catppuccin's Mocha flavor before the other
// three flavors were added.
catppuccin: 'catppuccin-mocha',
};

export const isTheme = (value: unknown): value is Theme =>
typeof value === 'string' && (VALID_THEMES as readonly string[]).includes(value);

/**
* Normalize a stored theme value, returning null when it names no theme this
* build knows about so the caller can fall back to its own default.
*/
export const resolveTheme = (value: string | null): Theme | null => {
if (isTheme(value)) return value;
if (value !== null && value in LEGACY_THEME_ALIASES) return LEGACY_THEME_ALIASES[value];
return null;
};

/**
* Review Location preferences
*/
Expand Down
Loading
Loading