Pin the UI locale to the language setting and add theme + language selectors with a dark palette

This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 12:28:06 -04:00
1 parent b30b9a3bc1
commit d404e1e72a
18 files changed
+176 -11

No files matched your search

+14 -1
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { Shell, type RouteId } from "../shared/Shell";
import { Onboarding } from "./Onboarding";
import { useSnapshot } from "./useSnapshot";
@@ -22,6 +22,19 @@ export function App() {
// would flip to the shell mid-onboarding.
const [finishingSetup, setFinishingSetup] = useState(false);
const theme = snapshot?.uiPreferences.theme ?? "system";
useEffect(() => {
const media = window.matchMedia("(prefers-color-scheme: dark)");
const apply = () => {
const resolved = theme === "system" ? (media.matches ? "dark" : "light") : theme;
document.documentElement.dataset.theme = resolved;
};
apply();
if (theme !== "system") return;
media.addEventListener("change", apply);
return () => media.removeEventListener("change", apply);
}, [theme]);
if (!snapshot) {
return <div className="app-shell" style={{ display: "grid", placeItems: "center" }} />;
}
+1 -1
View File
@@ -54,7 +54,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
</aside>
<div className="main" style={{ flex: 1, minWidth: 0 }}>
<div className="content">
{tab === "general" && <WorkspacePanel organization={organization} workspaces={workspaces} projects={snapshot.projects.filter((p) => p.orgId === orgId)} orgId={orgId} actions={actions} />}
{tab === "general" && <WorkspacePanel organization={organization} workspaces={workspaces} projects={snapshot.projects.filter((p) => p.orgId === orgId)} orgId={orgId} uiPreferences={snapshot.uiPreferences} actions={actions} />}
{tab === "providers" && (
<ProvidersPanel
orgId={orgId}
@@ -1,5 +1,12 @@
import { useState, type FormEvent } from "react";
import type { OrganizationRecord, ProjectRecord, WorkspaceRecord } from "../../../../shared/domain";
import type {
OrganizationRecord,
ProjectRecord,
UiLanguage,
UiPreferences,
UiTheme,
WorkspaceRecord,
} from "../../../../shared/domain";
import { Button, Field, Icon, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import type { SnapshotActions } from "../../app/useSnapshot";
@@ -17,12 +24,14 @@ export function WorkspacePanel({
workspaces,
projects,
orgId,
uiPreferences,
actions,
}: {
organization: OrganizationRecord;
workspaces: WorkspaceRecord[];
projects: ProjectRecord[];
orgId: string;
uiPreferences: UiPreferences;
actions: SnapshotActions;
}) {
const [createOpen, setCreateOpen] = useState(false);
@@ -63,6 +72,37 @@ export function WorkspacePanel({
<KeyValue label="Description" value={organization.description || "—"} />
</div>
<div className="section-label" style={{ marginBottom: 8 }}>Appearance</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 240px))", gap: 12, marginBottom: 28 }}>
<Field label="Theme">
<select
className="input"
aria-label="Theme"
value={uiPreferences.theme}
onChange={(event) =>
void actions.run(() => window.agentCompany.setUiPreferences({ theme: event.target.value as UiTheme }))
}
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</Field>
<Field label="Language">
<select
className="input"
aria-label="Language"
value={uiPreferences.language}
onChange={(event) =>
void actions.run(() => window.agentCompany.setUiPreferences({ language: event.target.value as UiLanguage }))
}
>
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</Field>
</div>
<div className="row reveal-row" style={{ marginBottom: 8 }}>
<div className="section-label">Workspaces</div>
<span className="reveal" style={{ marginLeft: "auto" }}>
+38
View File
@@ -63,6 +63,44 @@
--motion-slow: 280ms;
--ease-out: cubic-bezier(0.25, 1, 0.5, 1);
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
/* Primary action — inverts against the theme */
--btn-primary-bg: var(--text-1);
--btn-primary-fg: #ffffff;
--btn-primary-hover: #2c2c2c;
color-scheme: light;
}
:root[data-theme="dark"] {
--bg: #131313;
--panel: #1b1b1b;
--panel-2: #212121;
--panel-3: #2a2a2a;
--hover: #232323;
--border: #2c2c2c;
--border-strong: #3a3a3a;
--border-soft: #242424;
--text-1: #ededed;
--text-2: #a5a5a5;
--text-3: #7c7c7c;
--text-4: #4f4f4f;
--accent: #7c88e8;
--accent-soft: #262a4a;
--green: #45b06d;
--green-soft: #16301f;
--amber: #d99a3d;
--amber-soft: #33270f;
--red: #e05252;
--shadow-sm: 0 1px 0 rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.35);
--shadow-md: 0 1px 3px rgba(0, 0, 0, 0.4), 0 6px 24px rgba(0, 0, 0, 0.35);
--btn-primary-bg: #ededed;
--btn-primary-fg: #131313;
--btn-primary-hover: #ffffff;
color-scheme: dark;
}
* { box-sizing: border-box; }
+2 -2
View File
@@ -33,8 +33,8 @@
}
.button:hover { background: var(--panel-2); }
.button:active:not(:disabled) { transform: scale(0.97); }
.button.primary { background: var(--text-1); color: #fff; border-color: var(--text-1); }
.button.primary:hover { background: #2c2c2c; }
.button.primary { background: var(--btn-primary-bg); color: var(--btn-primary-fg); border-color: var(--btn-primary-bg); }
.button.primary:hover { background: var(--btn-primary-hover); }
.button.ghost { border-color: transparent; background: transparent; color: var(--text-2); }
.button.ghost:hover { background: var(--hover); color: var(--text-1); }
.button.small { height: 28px; padding: 0 var(--sp-2); font-size: var(--fs-caption); }