Pin the UI locale to the language setting and add theme + language selectors with a dark palette
This commit is contained in:
1 parent
b30b9a3bc1
commit
d404e1e72a
18 files changed
+176
-11
No files matched your search
@@ -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" }} />;
|
||||
}
|
||||
|
||||
@@ -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" }}>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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); }
|
||||
|
||||
Reference in new issue
Block a user