Sidebar polish round. Menus. `dropdown-menu` and `context-menu` were two copies of one engine that differed only in how the trigger fires — identical from `computePosition` down — and neither could nest. Both are now thin wrappers over `components/ui/menu/`, which owns anchoring, a pure page-stack navigation model, and one presentation fork: flyout on pointer, pushed pages in a sheet on compact. All 31 call sites are unchanged. Items got a rounded inset fill, a visible separator, and `selected` (a check) split from `active` (a fill). Sidebar display preferences. The flat wall of four sections becomes one row per decision with its value, and per-feature toggles over what a workspace row shows: host, pull request, checks, scripts. Each is independent — a ticked toggle that draws nothing is a lie. `display-preferences/` hides that grouping lives in zustand while title source and row items are app settings. Timestamps. "Last activity" now ticks itself. The label rounds to the minute and never shows seconds; past a week it becomes a date. The timer lives in the leaf `<Text>`, at most one per resolution tier shared by every row, and a tick only re-renders when the string actually changed — so a memoized row that legitimately never re-renders still stays current. Colour. Full status tier regenerated at 90% of gamut max (was 85), same rule as the muted tier, lightness and hue untouched. File icons are pulled toward neutral in OKLab behind `getFileIconSvg` — material-icon-theme picks its palette for a tree where the icon is the only colour on the row, and ours is not. New/deleted files use the forge square-plus/square-minus glyphs at muted weight instead of tinted pills. Project icons round a little more; uploaded icons never do. The project status badge is a knockout again: it fills with the colour of the row behind it, so the ring reads as a gap in the icon rather than a white halo. File rows. The always-visible kebab is gone from the file explorer and diff tree in favour of right-click, which also reclaims the reserved action slot.
673 lines
17 KiB
TypeScript
673 lines
17 KiB
TypeScript
import { Platform } from "react-native";
|
||
import { darkHighlightColors, lightHighlightColors } from "@getpaseo/highlight";
|
||
|
||
export const baseColors = {
|
||
// Base colors
|
||
white: "#ffffff",
|
||
black: "#000000",
|
||
|
||
// Zinc scale (primary gray palette)
|
||
zinc: {
|
||
50: "#fafafa",
|
||
100: "#f4f4f5",
|
||
200: "#e4e4e7",
|
||
300: "#d4d4d8",
|
||
400: "#a1a1aa",
|
||
500: "#71717a",
|
||
600: "#52525b",
|
||
700: "#3f3f46",
|
||
800: "#27272a",
|
||
850: "#1a1a1d",
|
||
900: "#18181b",
|
||
950: "#121214",
|
||
},
|
||
|
||
// Gray scale
|
||
gray: {
|
||
50: "#f9fafb",
|
||
100: "#f3f4f6",
|
||
200: "#e5e7eb",
|
||
300: "#d1d5db",
|
||
400: "#9ca3af",
|
||
500: "#6b7280",
|
||
600: "#4b5563",
|
||
700: "#374151",
|
||
800: "#1f2937",
|
||
900: "#111827",
|
||
},
|
||
|
||
// Slate scale
|
||
slate: {
|
||
200: "#e2e8f0",
|
||
},
|
||
|
||
// Blue scale
|
||
blue: {
|
||
50: "#eff6ff",
|
||
100: "#dbeafe",
|
||
200: "#bfdbfe",
|
||
300: "#93c5fd",
|
||
400: "#60a5fa",
|
||
500: "#3b82f6",
|
||
600: "#2563eb",
|
||
700: "#1d4ed8",
|
||
800: "#1e40af",
|
||
900: "#1e3a8a",
|
||
950: "#172554",
|
||
},
|
||
|
||
// Green scale
|
||
green: {
|
||
100: "#dcfce7",
|
||
200: "#bbf7d0",
|
||
400: "#4ade80",
|
||
500: "#22c55e",
|
||
600: "#16a34a",
|
||
800: "#166534",
|
||
900: "#14532d",
|
||
},
|
||
|
||
// Red scale
|
||
red: {
|
||
100: "#fee2e2",
|
||
200: "#fecaca",
|
||
300: "#fca5a5",
|
||
500: "#ef4444",
|
||
600: "#dc2626",
|
||
800: "#991b1b",
|
||
900: "#7f1d1d",
|
||
},
|
||
|
||
// Teal scale
|
||
teal: {
|
||
200: "#99f6e4",
|
||
},
|
||
|
||
// Amber scale
|
||
amber: {
|
||
500: "#f59e0b",
|
||
700: "#b45309",
|
||
},
|
||
|
||
// Yellow scale
|
||
yellow: {
|
||
400: "#fbbf24",
|
||
},
|
||
|
||
// Purple scale
|
||
purple: {
|
||
500: "#a855f7",
|
||
600: "#9333ea",
|
||
},
|
||
|
||
// Orange scale
|
||
orange: {
|
||
500: "#f97316",
|
||
600: "#ea580c",
|
||
},
|
||
} as const;
|
||
|
||
export type ThemeName = "light" | "dark" | "zinc" | "midnight" | "claude" | "ghostty";
|
||
|
||
// Diff colors — the +/- inside a diff view, where the color *is* the signal and has to
|
||
// survive being scanned line by line, so it stays saturated. Light uses muted tones, dark
|
||
// uses the brighter palette values.
|
||
//
|
||
// A diff *stat* — the "+12 −3" footnote next to a title — is not this. It reads as one of
|
||
// several muted signals in a row, so it uses statusMutedSuccess/Danger below rather than a
|
||
// near-duplicate tier of its own.
|
||
const lightDiffColors = {
|
||
diffAddition: "#15803d", // green-700 — readable on white without screaming
|
||
diffDeletion: "#b91c1c", // red-700
|
||
};
|
||
|
||
const darkDiffColors = {
|
||
diffAddition: "#4ade80", // green-400
|
||
diffDeletion: "#ef4444", // red-500
|
||
};
|
||
|
||
// Status colors — semantic signals for success/danger/warning/merged. Used by check
|
||
// statuses, PR states, and review decisions.
|
||
//
|
||
// Normalized, not hand-picked. Every color below shares one lightness and one chroma within
|
||
// its tier; only the hue changes, and each hue is the one that family already had. Chroma is
|
||
// a fixed fraction of what sRGB allows at that lightness and hue, because the gamut is
|
||
// lopsided — amber runs out of room long before red does, so a literal equal-chroma set
|
||
// leaves amber flat and red screaming. Equal fractions is what makes four hues read as one
|
||
// family. Regenerate with the same rule rather than nudging one value.
|
||
//
|
||
// Two tiers. Full is for a surface whose job is to report the status — the PR pane, a usage
|
||
// bar. Muted is for the same signal repeated on every row of a dense list, where full
|
||
// saturation makes the list itself the loudest thing on screen; it sits at roughly
|
||
// foregroundMuted's weight so a row built from it reads as one line of subtitle.
|
||
//
|
||
// Deliberately not on this scale: the workspace status dot. That one is supposed to shout,
|
||
// so it keeps the raw palette — see getStatusDotColor.
|
||
const lightStatusColors = {
|
||
// L=0.53, chroma 90% of gamut max
|
||
statusSuccess: "#1f8040",
|
||
statusDanger: "#c32424",
|
||
statusWarning: "#91601b",
|
||
statusMerged: "#8728e5",
|
||
// L=0.50, chroma 45% of gamut max
|
||
statusMutedSuccess: "#496d50",
|
||
statusMutedDanger: "#904d46",
|
||
statusMutedWarning: "#755f45",
|
||
statusMutedMerged: "#6e519d",
|
||
};
|
||
|
||
const darkStatusColors = {
|
||
// L=0.66, chroma 90% of gamut max
|
||
statusSuccess: "#2ead58",
|
||
statusDanger: "#f65048",
|
||
statusWarning: "#c38328",
|
||
statusMerged: "#a670f5",
|
||
// L=0.70, chroma 40% of gamut max
|
||
statusMutedSuccess: "#7cac85",
|
||
statusMutedDanger: "#ca8c85",
|
||
statusMutedWarning: "#b79875",
|
||
statusMutedMerged: "#a594c6",
|
||
};
|
||
|
||
// Semantic color tokens - Layer-based system
|
||
const lightSemanticColors = {
|
||
// Surfaces (layers) - shifted one step lighter
|
||
surface0: "#ffffff", // App background
|
||
surface1: "#fafafa", // Subtle hover (was zinc-100, now zinc-50)
|
||
surface2: "#f4f4f5", // Elevated: badges, inputs, sheets (was zinc-200, now zinc-100)
|
||
surface3: "#e4e4e7", // Highest elevation (was zinc-300, now zinc-200)
|
||
surface4: "#d4d4d8", // Extra emphasis (was zinc-400, now zinc-300)
|
||
surfaceDiffEmpty: "#f6f6f6", // Empty side of split diff rows, between surface1 and surface2 and biased toward surface2
|
||
surfaceSidebar: "#f4f4f5", // Sidebar background (darker than main)
|
||
surfaceSidebarHover: "#e9e9ec", // Sidebar hover (darker in light mode)
|
||
surfaceWorkspace: "#ffffff", // Workspace main background
|
||
|
||
// Text
|
||
foreground: "#1a1a1e",
|
||
foregroundMuted: "#71717a",
|
||
foregroundExtraMuted: "#a1a1aa",
|
||
|
||
// Controls
|
||
scrollbarHandle: "#3f3f46", // zinc-700
|
||
|
||
// Borders - shifted one step lighter
|
||
border: "#e4e4e7", // (was zinc-200, now zinc-200 - keep for contrast)
|
||
borderAccent: "#ececf1", // Softer accent border for low-emphasis outlines
|
||
|
||
// Brand
|
||
accent: "#20744A",
|
||
accentBright: "#239956",
|
||
accentForeground: "#ffffff",
|
||
|
||
// Semantic
|
||
destructive: "#b04138", // dark warm red on white — calm but unambiguously red
|
||
destructiveForeground: "#ffffff",
|
||
success: "#20744A",
|
||
successForeground: "#ffffff",
|
||
|
||
// Legacy aliases (for gradual migration)
|
||
background: "#ffffff",
|
||
popover: "#ffffff",
|
||
popoverForeground: "#1a1a1e",
|
||
primary: "#18181b",
|
||
primaryForeground: "#fafafa",
|
||
secondary: "#f4f4f5",
|
||
secondaryForeground: "#1a1a1e",
|
||
muted: "#f4f4f5",
|
||
mutedForeground: "#71717a",
|
||
accentBorder: "#ececf1",
|
||
input: "#f4f4f5",
|
||
ring: "#18181b",
|
||
|
||
...lightDiffColors,
|
||
...lightStatusColors,
|
||
|
||
terminal: {
|
||
background: "#ffffff",
|
||
foreground: "#1a1a1e",
|
||
cursor: "#1a1a1e",
|
||
cursorAccent: "#ffffff",
|
||
selectionBackground: "rgba(0, 0, 0, 0.15)",
|
||
selectionForeground: "#1a1a1e",
|
||
|
||
black: "#1a1a1e",
|
||
red: "#dc2626",
|
||
green: "#16a34a",
|
||
yellow: "#ca8a04",
|
||
blue: "#2563eb",
|
||
magenta: "#9333ea",
|
||
cyan: "#0891b2",
|
||
white: "#ffffff",
|
||
|
||
brightBlack: "#3f3f46",
|
||
brightRed: "#ef4444",
|
||
brightGreen: "#22c55e",
|
||
brightYellow: "#f59e0b",
|
||
brightBlue: "#3b82f6",
|
||
brightMagenta: "#a855f7",
|
||
brightCyan: "#06b6d4",
|
||
brightWhite: "#fafafa",
|
||
},
|
||
} as const;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Dark theme variant builder
|
||
// ---------------------------------------------------------------------------
|
||
|
||
interface DarkThemeConfig {
|
||
surface0: string;
|
||
surface1: string;
|
||
surface2: string;
|
||
surface3: string;
|
||
surface4: string;
|
||
surfaceDiffEmpty: string;
|
||
surfaceSidebar: string;
|
||
surfaceSidebarHover: string;
|
||
foregroundMuted: string;
|
||
foregroundExtraMuted: string;
|
||
scrollbarHandle: string;
|
||
border: string;
|
||
borderAccent: string;
|
||
accent: string;
|
||
accentBright: string;
|
||
accentForeground?: string;
|
||
destructive: string;
|
||
}
|
||
|
||
const darkTerminalAnsi = {
|
||
red: "#e07070",
|
||
green: "#5dba80",
|
||
yellow: "#d4a44a",
|
||
blue: "#6a9de0",
|
||
magenta: "#b07ad0",
|
||
cyan: "#4aabb8",
|
||
white: "#d4d4d8",
|
||
brightRed: "#e89090",
|
||
brightGreen: "#7ecf9a",
|
||
brightYellow: "#e0be6e",
|
||
brightBlue: "#8ab4e8",
|
||
brightMagenta: "#c49ae0",
|
||
brightCyan: "#6ec2cc",
|
||
brightWhite: "#f0f0f2",
|
||
} as const;
|
||
|
||
function buildDarkSemanticColors(tint: DarkThemeConfig) {
|
||
return {
|
||
surface0: tint.surface0,
|
||
surface1: tint.surface1,
|
||
surface2: tint.surface2,
|
||
surface3: tint.surface3,
|
||
surface4: tint.surface4,
|
||
surfaceDiffEmpty: tint.surfaceDiffEmpty,
|
||
surfaceSidebar: tint.surfaceSidebar,
|
||
surfaceSidebarHover: tint.surfaceSidebarHover,
|
||
surfaceWorkspace: tint.surface1,
|
||
|
||
foreground: "#fafafa",
|
||
foregroundMuted: tint.foregroundMuted,
|
||
foregroundExtraMuted: tint.foregroundExtraMuted,
|
||
|
||
scrollbarHandle: tint.scrollbarHandle,
|
||
|
||
border: tint.border,
|
||
borderAccent: tint.borderAccent,
|
||
|
||
accent: tint.accent,
|
||
accentBright: tint.accentBright,
|
||
accentForeground: tint.accentForeground ?? "#ffffff",
|
||
|
||
destructive: tint.destructive,
|
||
destructiveForeground: "#ffffff",
|
||
success: tint.accent,
|
||
successForeground: "#ffffff",
|
||
|
||
// Legacy aliases (for gradual migration)
|
||
background: tint.surface0,
|
||
popover: tint.surface2,
|
||
popoverForeground: "#fafafa",
|
||
primary: "#fafafa",
|
||
primaryForeground: tint.surface0,
|
||
secondary: tint.surface2,
|
||
secondaryForeground: "#fafafa",
|
||
muted: tint.surface2,
|
||
mutedForeground: tint.foregroundMuted,
|
||
accentBorder: tint.borderAccent,
|
||
input: tint.surface2,
|
||
ring: "#d4d4d8",
|
||
|
||
...darkDiffColors,
|
||
...darkStatusColors,
|
||
|
||
terminal: {
|
||
background: tint.surface0,
|
||
foreground: "#fafafa",
|
||
cursor: "#fafafa",
|
||
cursorAccent: tint.surface0,
|
||
selectionBackground: "rgba(255, 255, 255, 0.2)",
|
||
selectionForeground: "#fafafa",
|
||
black: tint.surfaceSidebar,
|
||
...darkTerminalAnsi,
|
||
brightBlack: tint.surface3,
|
||
},
|
||
};
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Dark tint definitions
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// Paseo — subtle teal-green tint (default)
|
||
const paseoDarkColors = buildDarkSemanticColors({
|
||
surface0: "#181B1A",
|
||
surface1: "#1E2120",
|
||
surface2: "#272A29",
|
||
surface3: "#434645",
|
||
surface4: "#595B5B",
|
||
surfaceDiffEmpty: "#252827",
|
||
surfaceSidebar: "#141716",
|
||
surfaceSidebarHover: "#1c1f1e",
|
||
foregroundMuted: "#A1A5A4",
|
||
foregroundExtraMuted: "#717574",
|
||
scrollbarHandle: "#717574",
|
||
border: "#252B2A",
|
||
borderAccent: "#2F3534",
|
||
accent: "#20744A",
|
||
accentBright: "#7ccba0",
|
||
destructive: "#c64f43", // warm red, hue ~7 — reads as red (not pink) against the green tint
|
||
});
|
||
|
||
// Zinc — neutral gray, no tint
|
||
const zincDarkColors = buildDarkSemanticColors({
|
||
surface0: "#18181b",
|
||
surface1: "#1f1f22",
|
||
surface2: "#27272a",
|
||
surface3: "#3f3f46",
|
||
surface4: "#52525b",
|
||
surfaceDiffEmpty: "#242427",
|
||
surfaceSidebar: "#131316",
|
||
surfaceSidebarHover: "#1b1b1e",
|
||
foregroundMuted: "#a1a1aa",
|
||
foregroundExtraMuted: "#71717a",
|
||
scrollbarHandle: "#71717a",
|
||
border: "#27272a",
|
||
borderAccent: "#303036",
|
||
accent: "#e4e4e7",
|
||
accentBright: "#fafafa",
|
||
accentForeground: "#18181b", // monochrome zinc accent is near-white — needs dark text
|
||
destructive: "#c44a4a", // neutral red, hue 0 — clearly red without screaming
|
||
});
|
||
|
||
// Midnight — subtle blue tint
|
||
const midnightDarkColors = buildDarkSemanticColors({
|
||
surface0: "#161820",
|
||
surface1: "#1c1e27",
|
||
surface2: "#252731",
|
||
surface3: "#3c3e4c",
|
||
surface4: "#535564",
|
||
surfaceDiffEmpty: "#222430",
|
||
surfaceSidebar: "#121420",
|
||
surfaceSidebarHover: "#1a1c28",
|
||
foregroundMuted: "#9a9db0",
|
||
foregroundExtraMuted: "#6b6e82",
|
||
scrollbarHandle: "#6b6e82",
|
||
border: "#242636",
|
||
borderAccent: "#2e3040",
|
||
accent: "#3b6fcf",
|
||
accentBright: "#7eaaeb",
|
||
destructive: "#c44a52", // red with a hint of cool lean against the blue tint
|
||
});
|
||
|
||
// Claude — warm neutral with subtle orange undertone
|
||
const claudeDarkColors = buildDarkSemanticColors({
|
||
surface0: "#1f1f1e",
|
||
surface1: "#262523",
|
||
surface2: "#2f2d2b",
|
||
surface3: "#4a4745",
|
||
surface4: "#605d5b",
|
||
surfaceDiffEmpty: "#2a2826",
|
||
surfaceSidebar: "#1a1918",
|
||
surfaceSidebarHover: "#222120",
|
||
foregroundMuted: "#ada9a5",
|
||
foregroundExtraMuted: "#78746f",
|
||
scrollbarHandle: "#78746f",
|
||
border: "#2c2a27",
|
||
borderAccent: "#36332f",
|
||
accent: "#d97757",
|
||
accentBright: "#e89a7f",
|
||
destructive: "#cf513e", // warm orange-red, hue ~10 — sits with the Claude orange accent
|
||
});
|
||
|
||
// Ghostty — blue-tinted dark based on Ghostty default background
|
||
const ghosttyDarkColors = buildDarkSemanticColors({
|
||
surface0: "#282c34",
|
||
surface1: "#2f333d",
|
||
surface2: "#383c48",
|
||
surface3: "#4a4f5e",
|
||
surface4: "#5b6175",
|
||
surfaceDiffEmpty: "#323643",
|
||
surfaceSidebar: "#21252d",
|
||
surfaceSidebarHover: "#292d36",
|
||
foregroundMuted: "#c8ccd8",
|
||
foregroundExtraMuted: "#a0a4b2",
|
||
scrollbarHandle: "#a0a4b2",
|
||
border: "#353a47",
|
||
borderAccent: "#3f4454",
|
||
accent: "#89b4fa",
|
||
accentBright: "#b4d0fc",
|
||
destructive: "#c44a55", // red with slight cool lean against the slate-blue surfaces
|
||
});
|
||
|
||
export const SPACING = {
|
||
0: 0,
|
||
1: 4,
|
||
1.5: 6,
|
||
2: 8,
|
||
3: 12,
|
||
4: 16,
|
||
6: 24,
|
||
8: 32,
|
||
12: 48,
|
||
16: 64,
|
||
20: 80,
|
||
24: 96,
|
||
32: 128,
|
||
} as const;
|
||
|
||
export const FONT_SIZE = {
|
||
xs: 12,
|
||
code: 12,
|
||
sm: 14,
|
||
base: 16,
|
||
lg: 18,
|
||
xl: 20,
|
||
"2xl": 22,
|
||
"3xl": 26,
|
||
"4xl": 34,
|
||
} as const;
|
||
|
||
export const LINE_HEIGHT = {
|
||
diff: 22,
|
||
} as const;
|
||
|
||
export const ICON_SIZE = {
|
||
xs: 12,
|
||
sm: 14,
|
||
md: 16,
|
||
lg: 20,
|
||
} as const;
|
||
|
||
export const FONT_WEIGHT = {
|
||
normal: "normal" as const,
|
||
medium: "500" as const,
|
||
semibold: "600" as const,
|
||
bold: "bold" as const,
|
||
} as const;
|
||
|
||
export const BORDER_RADIUS = {
|
||
none: 0,
|
||
sm: 2,
|
||
base: 4,
|
||
md: 6,
|
||
lg: 8,
|
||
xl: 12,
|
||
"2xl": 16,
|
||
full: 9999,
|
||
} as const;
|
||
|
||
export const BORDER_WIDTH = {
|
||
0: 0,
|
||
1: 1,
|
||
2: 2,
|
||
} as const;
|
||
|
||
export const OPACITY = {
|
||
0: 0,
|
||
50: 0.5,
|
||
100: 1,
|
||
} as const;
|
||
|
||
// 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,
|
||
};
|
||
|
||
const darkShadow = {
|
||
sm: {
|
||
shadowColor: "rgba(0, 0, 0, 0.25)",
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowRadius: 4,
|
||
elevation: 2,
|
||
},
|
||
md: {
|
||
shadowColor: "rgba(0, 0, 0, 0.20)",
|
||
shadowOffset: { width: 0, height: 4 },
|
||
shadowRadius: 8,
|
||
elevation: 8,
|
||
},
|
||
lg: {
|
||
shadowColor: "rgba(0, 0, 0, 0.40)",
|
||
shadowOffset: { width: 0, height: 12 },
|
||
shadowRadius: 24,
|
||
elevation: 8,
|
||
},
|
||
} as const;
|
||
|
||
function buildDarkTheme(semanticColors: ReturnType<typeof buildDarkSemanticColors>) {
|
||
return {
|
||
colorScheme: "dark" as const,
|
||
colors: {
|
||
...semanticColors,
|
||
palette: baseColors,
|
||
syntax: darkHighlightColors,
|
||
},
|
||
shadow: darkShadow,
|
||
...commonTheme,
|
||
} as const;
|
||
}
|
||
|
||
export const darkTheme = buildDarkTheme(paseoDarkColors);
|
||
export const darkZincTheme = buildDarkTheme(zincDarkColors);
|
||
export const darkMidnightTheme = buildDarkTheme(midnightDarkColors);
|
||
export const darkClaudeTheme = buildDarkTheme(claudeDarkColors);
|
||
export const darkGhosttyTheme = buildDarkTheme(ghosttyDarkColors);
|
||
|
||
export const lightTheme = {
|
||
colorScheme: "light" as const,
|
||
colors: {
|
||
...lightSemanticColors,
|
||
palette: baseColors,
|
||
syntax: lightHighlightColors,
|
||
},
|
||
shadow: {
|
||
sm: {
|
||
shadowColor: "rgba(0, 0, 0, 0.02)",
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowRadius: 8,
|
||
elevation: 2,
|
||
},
|
||
md: {
|
||
shadowColor: "rgba(0, 0, 0, 0.04)",
|
||
shadowOffset: { width: 0, height: 4 },
|
||
shadowRadius: 16,
|
||
elevation: 4,
|
||
},
|
||
lg: {
|
||
shadowColor: "rgba(0, 0, 0, 0.08)",
|
||
shadowOffset: { width: 0, height: 8 },
|
||
shadowRadius: 24,
|
||
elevation: 8,
|
||
},
|
||
},
|
||
...commonTheme,
|
||
} as const;
|
||
|
||
// Keep compatibility with existing code
|
||
export const theme = darkTheme;
|
||
|
||
// Export a union type that works for both themes
|
||
export type Theme = typeof darkTheme | typeof lightTheme;
|
||
|
||
type UnistylesThemeKey =
|
||
| "light"
|
||
| "dark"
|
||
| "darkZinc"
|
||
| "darkMidnight"
|
||
| "darkClaude"
|
||
| "darkGhostty";
|
||
|
||
export const THEME_TO_UNISTYLES: Record<ThemeName, UnistylesThemeKey> = {
|
||
light: "light",
|
||
dark: "dark",
|
||
zinc: "darkZinc",
|
||
midnight: "darkMidnight",
|
||
claude: "darkClaude",
|
||
ghostty: "darkGhostty",
|
||
};
|
||
|
||
export const THEME_SWATCHES: Record<ThemeName, string> = {
|
||
light: "#ffffff",
|
||
dark: "#2D8B62",
|
||
zinc: "#808080",
|
||
midnight: "#4A6BA8",
|
||
claude: "#D97757",
|
||
ghostty: "#8caaee",
|
||
};
|