Add appearance settings for theme, fonts, and syntax highlighting (#1236)
* Add appearance settings for theme, fonts, and syntax highlighting New Appearance section in Settings to configure the app theme, UI and mono font family + size, and a syntax-highlighting theme chosen independently of light/dark. Font family fields default to empty and show the active system stack as the placeholder; a live split-diff preview reflects the choices as they change. The theme picker moves here from General. Preferences are client-only (AsyncStorage) and applied at runtime by patching every registered Unistyles theme via UnistylesRuntime.updateTheme, so existing StyleSheet consumers repaint with no per-component preference reads and no useUnistyles. No daemon/protocol change. * Expand syntax themes and move the preview under the chooser Replace the small fixed syntax-theme set with eight popular themes — GitHub, Catppuccin, Dracula, Tokyo Night, One, Nord, Gruvbox, Solarized — built from a compact per-theme role palette. The app theme and syntax theme stay separate; the only coupling is the light/dark axis: a theme with both variants uses its light palette on a light app and its dark palette on a dark app, while the code frame (gutter, line numbers, background) keeps following the app theme. Default is now GitHub (GitHub keeps its existing hand-tuned maps for an exact match). Move the live preview directly under the syntax-theme chooser, drop the "preview.ts" filename header, and use a clearer code snippet for it. * Update settings e2e for the moved theme picker and Appearance section The theme picker moved from General to the new Appearance section, so the General-content assertion no longer finds "Theme" — point it at "Default send" (which stays in General) and add Appearance to the e2e section map plus an expectAppearanceContent check, and exercise the Appearance section in the sidebar-navigation test. * Render the appearance preview as a unified diff Switch the live preview from side-by-side columns to a single unified diff: unchanged context lines plus "-"/"+" rows for the change, matching how diffs read elsewhere in the app. Same snippet, mono font, syntax colors, and live code-font draft behavior. * Fix code font size change shrinking all UI text applyAppearance scaled the live (already-patched) theme ramp instead of the authored FONT_SIZE ramp, so it was not idempotent: every appearance change re-scaled the current sizes, compounding the whole-UI font scale. With any non-default UI size, changing the code size — or any setting — shrank (or grew) all app text, and it persisted into the theme. Build the ramp from the canonical FONT_SIZE ramp each apply (code size still set absolutely), so applies are idempotent and a code-size change never touches the UI ramp. * Apply the interface font across the app on web react-native-web stamps a default font onto every text element, so the interface font only reached the few components that set a font explicitly. Inject one rule that points all text at a CSS variable (updated live as the setting changes), with high specificity (1,2,0) so it deterministically beats RN-web's base font and Unistyles' generated classes — no stylesheet-order fragility. Code, diff, and monospace surfaces carry a `data-pmono` marker (and their subtrees are excluded), so they keep their code font. Web-only: React Native has no global font cascade, so native still applies the UI font only where components opt in. Inline `code` within chat prose is a known minor gap (its render paths aren't tagged yet). * Keep preview code in the code font * Apply code font settings to terminals * Fix settings host picker import after rebase * Format terminal webview bundle
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
// Marker for code / diff / monospace surfaces. On web, the app-wide interface-font
|
||||
// rule (see screens/settings/appearance/apply-root-font.web.ts) targets
|
||||
// `#root *:not([data-pmono]):not([data-pmono] *)`, so tagging a code container with
|
||||
// this dataSet excludes it and its subtree — they keep their monospace font. On
|
||||
// native it renders nothing and is harmless. Use a shared stable reference so it
|
||||
// doesn't trip the react-perf "new object as prop" rule.
|
||||
export const CODE_SURFACE_DATASET = { pmono: "" } as const;
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Theme } from "./theme";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
|
||||
const webSelectableTextStyle = isWeb ? { userSelect: "text" as const } : {};
|
||||
@@ -21,7 +20,9 @@ export function createMarkdownStyles(theme: Theme) {
|
||||
...webSelectableTextStyle,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.base,
|
||||
lineHeight: 22,
|
||||
// Prose line-height scales with the UI ramp (≈22 at base 16), NOT the
|
||||
// code-size-coupled lineHeight.diff token used by code/diff surfaces.
|
||||
lineHeight: Math.round(theme.fontSize.base * 1.4),
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
width: "100%" as const,
|
||||
@@ -169,8 +170,8 @@ export function createMarkdownStyles(theme: Theme) {
|
||||
paddingVertical: 2,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
borderWidth: 0,
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.base - 3,
|
||||
fontFamily: theme.fontFamily.mono,
|
||||
fontSize: theme.fontSize.code,
|
||||
},
|
||||
|
||||
code_block: {
|
||||
@@ -179,7 +180,7 @@ export function createMarkdownStyles(theme: Theme) {
|
||||
color: theme.colors.foreground,
|
||||
padding: theme.spacing[3],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
fontFamily: Fonts.mono,
|
||||
fontFamily: theme.fontFamily.mono,
|
||||
fontSize: theme.fontSize.code,
|
||||
marginVertical: theme.spacing[2],
|
||||
},
|
||||
@@ -192,7 +193,7 @@ export function createMarkdownStyles(theme: Theme) {
|
||||
borderRadius: theme.borderRadius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
fontFamily: Fonts.mono,
|
||||
fontFamily: theme.fontFamily.mono,
|
||||
fontSize: theme.fontSize.code,
|
||||
marginVertical: theme.spacing[3],
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Platform } from "react-native";
|
||||
import { darkHighlightColors, lightHighlightColors } from "@getpaseo/highlight";
|
||||
|
||||
export const baseColors = {
|
||||
@@ -485,16 +486,48 @@ export const OPACITY = {
|
||||
100: 1,
|
||||
} as const;
|
||||
|
||||
const commonTheme = {
|
||||
// Platform default font stacks — copied verbatim from constants/theme.ts `Fonts`
|
||||
// (sans -> ui, mono -> mono). These seed the dynamic `fontFamily` theme token and
|
||||
// are the fallback an empty user-supplied family resolves to at apply time.
|
||||
export const DEFAULT_UI_FONT_STACK: string = Platform.select({
|
||||
ios: "system-ui",
|
||||
default: "normal",
|
||||
web: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
||||
});
|
||||
|
||||
export const DEFAULT_MONO_FONT_STACK: string = Platform.select({
|
||||
ios: "ui-monospace",
|
||||
default: "monospace",
|
||||
web: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
||||
});
|
||||
|
||||
// `fontSize`, `fontFamily`, and `lineHeight` are deliberately widened to plain
|
||||
// `number`/`string` (not narrowed by `as const`) so the appearance updater can patch
|
||||
// them at runtime via `UnistylesRuntime.updateTheme`. The remaining tokens keep their
|
||||
// literal types.
|
||||
interface CommonTheme {
|
||||
spacing: typeof SPACING;
|
||||
fontSize: Record<keyof typeof FONT_SIZE, number>;
|
||||
fontFamily: { ui: string; mono: string };
|
||||
lineHeight: Record<keyof typeof LINE_HEIGHT, number>;
|
||||
iconSize: typeof ICON_SIZE;
|
||||
fontWeight: typeof FONT_WEIGHT;
|
||||
borderRadius: typeof BORDER_RADIUS;
|
||||
borderWidth: typeof BORDER_WIDTH;
|
||||
opacity: typeof OPACITY;
|
||||
}
|
||||
|
||||
const commonTheme: CommonTheme = {
|
||||
spacing: SPACING,
|
||||
fontSize: FONT_SIZE,
|
||||
fontFamily: { ui: DEFAULT_UI_FONT_STACK, mono: DEFAULT_MONO_FONT_STACK },
|
||||
lineHeight: LINE_HEIGHT,
|
||||
iconSize: ICON_SIZE,
|
||||
fontWeight: FONT_WEIGHT,
|
||||
borderRadius: BORDER_RADIUS,
|
||||
borderWidth: BORDER_WIDTH,
|
||||
opacity: OPACITY,
|
||||
} as const;
|
||||
};
|
||||
|
||||
const darkShadow = {
|
||||
sm: {
|
||||
|
||||
Reference in New Issue
Block a user