feat: add theme selector with multiple dark themes
Add theme dropdown in settings with Light, Dark, System plus Zinc, Midnight, Claude, and Ghostty dark variants. Each theme has its own accent color and surface tints. Desaturate terminal ANSI colors across all dark themes. Add surfaceSidebarHover semantic token so hover states go the right direction in both light and dark modes. Self-heal if a stored theme no longer exists.
This commit is contained in:
@@ -14,6 +14,7 @@ import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
||||
import { PortalProvider } from "@gorhom/portal";
|
||||
import { VoiceProvider } from "@/contexts/voice-context";
|
||||
import { useAppSettings } from "@/hooks/use-settings";
|
||||
import { THEME_TO_UNISTYLES } from "@/styles/theme";
|
||||
import { useFaviconStatus } from "@/hooks/use-favicon-status";
|
||||
import { View, Text } from "react-native";
|
||||
import { UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||
@@ -561,7 +562,7 @@ function ProvidersWrapper({ children }: { children: ReactNode }) {
|
||||
UnistylesRuntime.setAdaptiveThemes(true);
|
||||
} else {
|
||||
UnistylesRuntime.setAdaptiveThemes(false);
|
||||
UnistylesRuntime.setTheme(settings.theme);
|
||||
UnistylesRuntime.setTheme(THEME_TO_UNISTYLES[settings.theme]);
|
||||
}
|
||||
}, [settingsLoading, settings.theme]);
|
||||
|
||||
|
||||
@@ -472,7 +472,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderRadius: theme.borderRadius.md,
|
||||
},
|
||||
tabActive: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
tabText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
|
||||
@@ -849,7 +849,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderRadius: theme.borderRadius.md,
|
||||
},
|
||||
entryRowActive: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
indentGuide: {
|
||||
position: "absolute",
|
||||
|
||||
@@ -885,10 +885,10 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
newAgentButtonHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
newAgentButtonActive: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
hostTrigger: {
|
||||
flexDirection: "row",
|
||||
@@ -901,7 +901,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
},
|
||||
hostTriggerHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
hostStatusDot: {
|
||||
width: 8,
|
||||
|
||||
@@ -1980,7 +1980,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
projectRowHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
projectRowPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
@@ -2050,7 +2050,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 0,
|
||||
},
|
||||
projectActionButtonHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
projectActionButtonText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
@@ -2065,7 +2065,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 0,
|
||||
},
|
||||
projectIconActionButtonHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
projectIconActionButtonHidden: {
|
||||
opacity: 0,
|
||||
@@ -2143,7 +2143,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 0,
|
||||
},
|
||||
workspaceRowHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
workspaceRowPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
@@ -2157,7 +2157,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
...theme.shadow.md,
|
||||
},
|
||||
sidebarRowSelected: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surfaceSidebarHover,
|
||||
},
|
||||
workspaceRowContainer: {
|
||||
position: "relative",
|
||||
|
||||
@@ -6,10 +6,14 @@ export const APP_SETTINGS_KEY = "@paseo:app-settings";
|
||||
const LEGACY_SETTINGS_KEY = "@paseo:settings";
|
||||
const APP_SETTINGS_QUERY_KEY = ["app-settings"];
|
||||
|
||||
import { THEME_TO_UNISTYLES, type ThemeName } from "@/styles/theme";
|
||||
|
||||
export type SendBehavior = "interrupt" | "queue";
|
||||
|
||||
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
||||
|
||||
export interface AppSettings {
|
||||
theme: "dark" | "light" | "auto";
|
||||
theme: ThemeName | "auto";
|
||||
manageBuiltInDaemon: boolean;
|
||||
sendBehavior: SendBehavior;
|
||||
}
|
||||
@@ -78,6 +82,9 @@ export async function loadSettingsFromStorage(): Promise<AppSettings> {
|
||||
const stored = await AsyncStorage.getItem(APP_SETTINGS_KEY);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored) as Partial<AppSettings>;
|
||||
if (parsed.theme && !VALID_THEMES.has(parsed.theme)) {
|
||||
parsed.theme = DEFAULT_APP_SETTINGS.theme;
|
||||
}
|
||||
return { ...DEFAULT_APP_SETTINGS, ...parsed };
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Sun,
|
||||
Moon,
|
||||
Monitor,
|
||||
ChevronDown,
|
||||
Globe,
|
||||
Settings,
|
||||
RotateCw,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
Smartphone,
|
||||
} from "lucide-react-native";
|
||||
import { useAppSettings, type AppSettings, type SendBehavior } from "@/hooks/use-settings";
|
||||
import { THEME_SWATCHES, type ThemeName } from "@/styles/theme";
|
||||
import type { HostProfile, HostConnection } from "@/types/host-connection";
|
||||
import { useHosts, useHostMutations } from "@/runtime/host-runtime";
|
||||
import { formatConnectionStatus, getConnectionStatusTone } from "@/utils/daemons";
|
||||
@@ -47,6 +49,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { AdaptiveModalSheet, AdaptiveTextInput } from "@/components/adaptive-modal-sheet";
|
||||
@@ -362,11 +365,53 @@ interface GeneralSectionProps {
|
||||
handleSendBehaviorChange: (behavior: SendBehavior) => void;
|
||||
}
|
||||
|
||||
function ThemeIcon({ theme, size, color }: { theme: AppSettings["theme"]; size: number; color: string }) {
|
||||
switch (theme) {
|
||||
case "light":
|
||||
return <Sun size={size} color={color} />;
|
||||
case "dark":
|
||||
return <Moon size={size} color={color} />;
|
||||
case "auto":
|
||||
return <Monitor size={size} color={color} />;
|
||||
default:
|
||||
return <ThemeSwatch color={THEME_SWATCHES[theme]} size={size} />;
|
||||
}
|
||||
}
|
||||
|
||||
function ThemeSwatch({ color, size }: { color: string; size: number }) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
backgroundColor: color,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255,255,255,0.15)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const THEME_LABELS: Record<AppSettings["theme"], string> = {
|
||||
light: "Light",
|
||||
dark: "Dark",
|
||||
zinc: "Zinc",
|
||||
midnight: "Midnight",
|
||||
claude: "Claude",
|
||||
ghostty: "Ghostty",
|
||||
auto: "System",
|
||||
};
|
||||
|
||||
function GeneralSection({
|
||||
settings,
|
||||
handleThemeChange,
|
||||
handleSendBehaviorChange,
|
||||
}: GeneralSectionProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const iconSize = theme.iconSize.md;
|
||||
const iconColor = theme.colors.foregroundMuted;
|
||||
|
||||
return (
|
||||
<View style={settingsStyles.section}>
|
||||
<Text style={settingsStyles.sectionTitle}>General</Text>
|
||||
@@ -375,29 +420,43 @@ function GeneralSection({
|
||||
<View style={styles.audioRowContent}>
|
||||
<Text style={styles.audioRowTitle}>Theme</Text>
|
||||
</View>
|
||||
<SegmentedControl
|
||||
size="sm"
|
||||
hideLabels={Platform.OS !== "web"}
|
||||
value={settings.theme}
|
||||
onValueChange={handleThemeChange}
|
||||
options={[
|
||||
{
|
||||
value: "light",
|
||||
label: "Light",
|
||||
icon: ({ color, size }) => <Sun size={size} color={color} />,
|
||||
},
|
||||
{
|
||||
value: "dark",
|
||||
label: "Dark",
|
||||
icon: ({ color, size }) => <Moon size={size} color={color} />,
|
||||
},
|
||||
{
|
||||
value: "auto",
|
||||
label: "System",
|
||||
icon: ({ color, size }) => <Monitor size={size} color={color} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
style={({ pressed }) => [
|
||||
styles.themeTrigger,
|
||||
pressed && { opacity: 0.85 },
|
||||
]}
|
||||
>
|
||||
<ThemeIcon theme={settings.theme} size={iconSize} color={iconColor} />
|
||||
<Text style={styles.themeTriggerText}>
|
||||
{THEME_LABELS[settings.theme]}
|
||||
</Text>
|
||||
<ChevronDown size={theme.iconSize.sm} color={iconColor} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end" width={200}>
|
||||
{(["light", "dark", "auto"] as const).map((t) => (
|
||||
<DropdownMenuItem
|
||||
key={t}
|
||||
selected={settings.theme === t}
|
||||
onSelect={() => handleThemeChange(t)}
|
||||
leading={<ThemeIcon theme={t} size={iconSize} color={iconColor} />}
|
||||
>
|
||||
{THEME_LABELS[t]}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
{(["zinc", "midnight", "claude", "ghostty"] as const).map((t) => (
|
||||
<DropdownMenuItem
|
||||
key={t}
|
||||
selected={settings.theme === t}
|
||||
onSelect={() => handleThemeChange(t)}
|
||||
leading={<ThemeIcon theme={t} size={iconSize} color={iconColor} />}
|
||||
>
|
||||
{THEME_LABELS[t]}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</View>
|
||||
<View style={styles.audioRow}>
|
||||
<View style={styles.audioRowContent}>
|
||||
@@ -1806,6 +1865,20 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
},
|
||||
themeTrigger: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
paddingVertical: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
},
|
||||
themeTriggerText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
disabled: {
|
||||
opacity: theme.opacity[50],
|
||||
},
|
||||
|
||||
@@ -103,6 +103,8 @@ export const baseColors = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type ThemeName = "light" | "dark" | "zinc" | "midnight" | "claude" | "ghostty";
|
||||
|
||||
// Semantic color tokens - Layer-based system
|
||||
const lightSemanticColors = {
|
||||
// Surfaces (layers) - shifted one step lighter
|
||||
@@ -113,10 +115,11 @@ const lightSemanticColors = {
|
||||
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: "#09090b",
|
||||
foreground: "#1a1a1e",
|
||||
foregroundMuted: "#71717a",
|
||||
|
||||
// Controls
|
||||
@@ -140,11 +143,11 @@ const lightSemanticColors = {
|
||||
// Legacy aliases (for gradual migration)
|
||||
background: "#ffffff",
|
||||
popover: "#ffffff",
|
||||
popoverForeground: "#09090b",
|
||||
popoverForeground: "#1a1a1e",
|
||||
primary: "#18181b",
|
||||
primaryForeground: "#fafafa",
|
||||
secondary: "#f4f4f5",
|
||||
secondaryForeground: "#09090b",
|
||||
secondaryForeground: "#1a1a1e",
|
||||
muted: "#f4f4f5",
|
||||
mutedForeground: "#71717a",
|
||||
accentBorder: "#ececf1",
|
||||
@@ -153,13 +156,13 @@ const lightSemanticColors = {
|
||||
|
||||
terminal: {
|
||||
background: "#ffffff",
|
||||
foreground: "#09090b",
|
||||
cursor: "#09090b",
|
||||
foreground: "#1a1a1e",
|
||||
cursor: "#1a1a1e",
|
||||
cursorAccent: "#ffffff",
|
||||
selectionBackground: "rgba(0, 0, 0, 0.15)",
|
||||
selectionForeground: "#09090b",
|
||||
selectionForeground: "#1a1a1e",
|
||||
|
||||
black: "#09090b",
|
||||
black: "#1a1a1e",
|
||||
red: "#dc2626",
|
||||
green: "#16a34a",
|
||||
yellow: "#ca8a04",
|
||||
@@ -179,80 +182,194 @@ const lightSemanticColors = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const darkSemanticColors = {
|
||||
// Surfaces (layers) — subtle teal tint
|
||||
surface0: "#181B1A", // App background
|
||||
surface1: "#1E2120", // Subtle hover
|
||||
surface2: "#272A29", // Elevated: badges, inputs, sheets
|
||||
surface3: "#434645", // Highest elevation
|
||||
surface4: "#595B5B", // Extra emphasis
|
||||
surfaceDiffEmpty: "#252827", // Empty side of split diff rows, between surface1 and surface2 and biased toward surface2
|
||||
surfaceSidebar: "#141716", // Sidebar background (darker than main)
|
||||
surfaceWorkspace: "#1E2120", // Workspace main background (surface1)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dark theme variant builder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Text
|
||||
foreground: "#fafafa",
|
||||
interface DarkThemeConfig {
|
||||
surface0: string;
|
||||
surface1: string;
|
||||
surface2: string;
|
||||
surface3: string;
|
||||
surface4: string;
|
||||
surfaceDiffEmpty: string;
|
||||
surfaceSidebar: string;
|
||||
surfaceSidebarHover: string;
|
||||
foregroundMuted: string;
|
||||
scrollbarHandle: string;
|
||||
border: string;
|
||||
borderAccent: string;
|
||||
accent: string;
|
||||
accentBright: 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,
|
||||
|
||||
scrollbarHandle: tint.scrollbarHandle,
|
||||
|
||||
border: tint.border,
|
||||
borderAccent: tint.borderAccent,
|
||||
|
||||
accent: tint.accent,
|
||||
accentBright: tint.accentBright,
|
||||
accentForeground: "#ffffff",
|
||||
|
||||
destructive: "#ef4444",
|
||||
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",
|
||||
|
||||
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",
|
||||
|
||||
// Controls
|
||||
scrollbarHandle: "#717574", // zinc-500 w/ teal tint
|
||||
|
||||
// Borders
|
||||
scrollbarHandle: "#717574",
|
||||
border: "#252B2A",
|
||||
borderAccent: "#2F3534",
|
||||
|
||||
// Brand
|
||||
accent: "#20744A",
|
||||
accentBright: "#7ccba0",
|
||||
accentForeground: "#ffffff",
|
||||
});
|
||||
|
||||
// Semantic
|
||||
destructive: "#ef4444",
|
||||
destructiveForeground: "#ffffff",
|
||||
success: "#20744A",
|
||||
successForeground: "#ffffff",
|
||||
// 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",
|
||||
scrollbarHandle: "#71717a",
|
||||
border: "#27272a",
|
||||
borderAccent: "#303036",
|
||||
accent: "#20744A",
|
||||
accentBright: "#7ccba0",
|
||||
});
|
||||
|
||||
// Legacy aliases (for gradual migration)
|
||||
background: "#181B1A",
|
||||
popover: "#272A29",
|
||||
popoverForeground: "#fafafa",
|
||||
primary: "#fafafa",
|
||||
primaryForeground: "#181B1A",
|
||||
secondary: "#272A29",
|
||||
secondaryForeground: "#fafafa",
|
||||
muted: "#272A29",
|
||||
mutedForeground: "#A1A5A4",
|
||||
accentBorder: "#2F3534",
|
||||
input: "#272A29",
|
||||
ring: "#d4d4d8",
|
||||
// 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",
|
||||
scrollbarHandle: "#6b6e82",
|
||||
border: "#242636",
|
||||
borderAccent: "#2e3040",
|
||||
accent: "#3b6fcf",
|
||||
accentBright: "#7eaaeb",
|
||||
});
|
||||
|
||||
terminal: {
|
||||
background: "#181B1A",
|
||||
foreground: "#fafafa",
|
||||
cursor: "#fafafa",
|
||||
cursorAccent: "#181B1A",
|
||||
selectionBackground: "rgba(255, 255, 255, 0.2)",
|
||||
selectionForeground: "#fafafa",
|
||||
// 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",
|
||||
scrollbarHandle: "#78746f",
|
||||
border: "#2c2a27",
|
||||
borderAccent: "#36332f",
|
||||
accent: "#d97757",
|
||||
accentBright: "#e89a7f",
|
||||
});
|
||||
|
||||
black: "#141716",
|
||||
red: "#ef4444",
|
||||
green: "#22c55e",
|
||||
yellow: "#f59e0b",
|
||||
blue: "#3b82f6",
|
||||
magenta: "#a855f7",
|
||||
cyan: "#06b6d4",
|
||||
white: "#e4e4e7",
|
||||
|
||||
brightBlack: "#434645",
|
||||
brightRed: "#f87171",
|
||||
brightGreen: "#4ade80",
|
||||
brightYellow: "#fbbf24",
|
||||
brightBlue: "#60a5fa",
|
||||
brightMagenta: "#c084fc",
|
||||
brightCyan: "#22d3ee",
|
||||
brightWhite: "#ffffff",
|
||||
},
|
||||
} as const;
|
||||
// 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",
|
||||
scrollbarHandle: "#a0a4b2",
|
||||
border: "#353a47",
|
||||
borderAccent: "#3f4454",
|
||||
accent: "#89b4fa",
|
||||
accentBright: "#b4d0fc",
|
||||
});
|
||||
|
||||
const commonTheme = {
|
||||
spacing: {
|
||||
@@ -323,35 +440,45 @@ const commonTheme = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const darkTheme = {
|
||||
colorScheme: "dark" as const,
|
||||
colors: {
|
||||
...darkSemanticColors,
|
||||
palette: baseColors,
|
||||
const darkShadow = {
|
||||
sm: {
|
||||
shadowColor: "rgba(0, 0, 0, 0.25)",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
},
|
||||
shadow: {
|
||||
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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
...commonTheme,
|
||||
} as const;
|
||||
|
||||
function buildDarkTheme(semanticColors: ReturnType<typeof buildDarkSemanticColors>) {
|
||||
return {
|
||||
colorScheme: "dark" as const,
|
||||
colors: {
|
||||
...semanticColors,
|
||||
palette: baseColors,
|
||||
},
|
||||
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: {
|
||||
@@ -386,3 +513,23 @@ 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",
|
||||
};
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
// import { UnistylesRuntime } from "react-native-unistyles";
|
||||
import { lightTheme, darkTheme } from "./theme";
|
||||
import {
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
darkZincTheme,
|
||||
darkMidnightTheme,
|
||||
darkClaudeTheme,
|
||||
darkGhosttyTheme,
|
||||
} from "./theme";
|
||||
|
||||
StyleSheet.configure({
|
||||
themes: {
|
||||
light: lightTheme,
|
||||
dark: darkTheme,
|
||||
darkZinc: darkZincTheme,
|
||||
darkMidnight: darkMidnightTheme,
|
||||
darkClaude: darkClaudeTheme,
|
||||
darkGhostty: darkGhosttyTheme,
|
||||
},
|
||||
breakpoints: {
|
||||
xs: 0,
|
||||
@@ -23,6 +33,10 @@ StyleSheet.configure({
|
||||
type AppThemes = {
|
||||
light: typeof lightTheme;
|
||||
dark: typeof darkTheme;
|
||||
darkZinc: typeof darkZincTheme;
|
||||
darkMidnight: typeof darkMidnightTheme;
|
||||
darkClaude: typeof darkClaudeTheme;
|
||||
darkGhostty: typeof darkGhosttyTheme;
|
||||
};
|
||||
|
||||
type AppBreakpoints = {
|
||||
@@ -37,5 +51,3 @@ declare module "react-native-unistyles" {
|
||||
export interface UnistylesThemes extends AppThemes {}
|
||||
export interface UnistylesBreakpoints extends AppBreakpoints {}
|
||||
}
|
||||
|
||||
// UnistylesRuntime.setRootViewBackgroundColor(lightTheme.colors.background);
|
||||
|
||||
Reference in New Issue
Block a user