Skip to content

Commit db788e6

Browse files
Apply PR #40845: feat(app): redesign non-modal settings
2 parents 9d55f23 + c64c289 commit db788e6

43 files changed

Lines changed: 1451 additions & 316 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/app/src/components/server/server-row-menu.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,10 @@ export const ServerRowMenuView: Component<{
7878
>
7979
{props.labels.edit}
8080
</MenuV2.Item>
81-
<Show when={props.canDefault && !props.isDefault}>
82-
<MenuV2.Item onSelect={props.onSetDefault}>{props.labels.default}</MenuV2.Item>
83-
</Show>
84-
<Show when={props.canDefault && props.isDefault}>
85-
<MenuV2.Item onSelect={props.onRemoveDefault}>{props.labels.defaultRemove}</MenuV2.Item>
81+
<Show when={props.canDefault}>
82+
<MenuV2.Item disabled={props.isDefault} onSelect={props.onSetDefault}>
83+
{props.labels.default}
84+
</MenuV2.Item>
8685
</Show>
8786
<MenuV2.Separator />
8887
<MenuV2.Item disabled={builtin()} onSelect={props.onRemove}>
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { Component, Show, createMemo, createResource } from "solid-js"
2+
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
3+
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
4+
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
5+
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
6+
import { useLanguage } from "@/context/language"
7+
import { usePlatform } from "@/context/platform"
8+
import { ExternalLink } from "../external-link"
9+
import { SettingsListV2 } from "./parts/list"
10+
import { SettingsRowV2 } from "./parts/row"
11+
import {
12+
createAppearanceSettingsController,
13+
type AppearanceSettingsController,
14+
} from "./general-controllers"
15+
import "./settings-v2.css"
16+
17+
const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"]
18+
const fontSettings = {
19+
ui: {
20+
action: "settings-ui-font",
21+
title: "settings.general.row.uiFont.title",
22+
description: "settings.general.row.uiFont.description",
23+
font: "ui",
24+
input: "setUI",
25+
},
26+
code: {
27+
action: "settings-code-font",
28+
title: "settings.general.row.font.title",
29+
description: "settings.general.row.font.description",
30+
font: "code",
31+
input: "setCode",
32+
},
33+
terminal: {
34+
action: "settings-terminal-font",
35+
title: "settings.general.row.terminalFont.title",
36+
description: "settings.general.row.terminalFont.description",
37+
font: "terminal",
38+
input: "setTerminal",
39+
},
40+
} as const
41+
42+
const FontSetting: Component<{
43+
kind: "ui" | "code" | "terminal"
44+
fonts: AppearanceSettingsController["fonts"]
45+
}> = (props) => {
46+
const language = useLanguage()
47+
const config = () => fontSettings[props.kind]
48+
return (
49+
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
50+
<div class="w-full sm:w-[220px]">
51+
<TextInputV2
52+
data-action={config().action}
53+
type="text"
54+
appearance="base"
55+
value={props.fonts[config().font]().value}
56+
onInput={(event) => props.fonts[config().input](event.currentTarget.value)}
57+
placeholder={props.fonts[config().font]().placeholder}
58+
spellcheck={false}
59+
autocorrect="off"
60+
autocomplete="off"
61+
autocapitalize="off"
62+
aria-label={language.t(config().title)}
63+
style={{ "font-family": props.fonts[config().font]().family }}
64+
/>
65+
</div>
66+
</SettingsRowV2>
67+
)
68+
}
69+
70+
export const SettingsAppearanceV2: Component = () => {
71+
const language = useLanguage()
72+
const platform = usePlatform()
73+
const appearance = createAppearanceSettingsController()
74+
const desktop = createMemo(() => platform.platform === "desktop")
75+
76+
const [pinchZoom, { mutate: setPinchZoom }] = createResource(
77+
() => desktop() && "getPinchZoomEnabled" in platform,
78+
() => Promise.resolve(platform.getPinchZoomEnabled?.() ?? false).catch(() => false),
79+
{ initialValue: false },
80+
)
81+
82+
const onPinchZoomChange = (checked: boolean) => {
83+
setPinchZoom(checked)
84+
const update = platform.setPinchZoomEnabled?.(checked)
85+
if (!update) return
86+
void update.catch(() => setPinchZoom(!checked))
87+
}
88+
89+
const restoreDefaults = () => {
90+
appearance.scheme.select("system")
91+
appearance.fonts.setUI("")
92+
appearance.fonts.setCode("")
93+
appearance.fonts.setTerminal("")
94+
}
95+
96+
return (
97+
<>
98+
<div class="settings-v2-tab-header">
99+
<div class="settings-v2-tab-header-row">
100+
<h2 class="settings-v2-tab-title">{language.t("settings.general.section.appearance")}</h2>
101+
<ButtonV2 size="small" variant="ghost-muted" onClick={restoreDefaults}>
102+
{language.t("common.reset")}
103+
</ButtonV2>
104+
</div>
105+
</div>
106+
107+
<div class="settings-v2-tab-body">
108+
<div class="settings-v2-section">
109+
<SettingsListV2>
110+
<SettingsRowV2
111+
title={language.t("settings.general.row.colorScheme.title")}
112+
description={language.t("settings.general.row.colorScheme.description")}
113+
>
114+
<SelectV2
115+
appearance="inline"
116+
data-action="settings-color-scheme"
117+
options={schemeOptions}
118+
current={schemeOptions.find((option) => option === appearance.scheme.current())}
119+
placement="bottom-end"
120+
gutter={6}
121+
label={(option) => {
122+
if (option === "system") return language.t("theme.scheme.system")
123+
if (option === "light") return language.t("theme.scheme.light")
124+
return language.t("theme.scheme.dark")
125+
}}
126+
onSelect={(option) => option && appearance.scheme.select(option)}
127+
/>
128+
</SettingsRowV2>
129+
130+
<SettingsRowV2
131+
title={language.t("settings.general.row.theme.title")}
132+
description={
133+
<>
134+
{language.t("settings.general.row.theme.description")}{" "}
135+
<ExternalLink class="settings-v2-link" href="https://opencode.ai/docs/themes/">
136+
{language.t("common.learnMore")}
137+
</ExternalLink>
138+
</>
139+
}
140+
>
141+
<SelectV2
142+
appearance="inline"
143+
data-action="settings-theme"
144+
options={appearance.theme.options()}
145+
current={appearance.theme.current()}
146+
placement="bottom-end"
147+
gutter={6}
148+
value={(option) => option.id}
149+
label={(option) => option.name}
150+
onSelect={appearance.theme.select}
151+
/>
152+
</SettingsRowV2>
153+
154+
<FontSetting kind="ui" fonts={appearance.fonts} />
155+
<FontSetting kind="code" fonts={appearance.fonts} />
156+
<FontSetting kind="terminal" fonts={appearance.fonts} />
157+
</SettingsListV2>
158+
</div>
159+
160+
<Show when={desktop()}>
161+
<div class="settings-v2-section">
162+
<h3 class="settings-v2-section-title">{language.t("settings.general.section.display")}</h3>
163+
<SettingsListV2>
164+
<SettingsRowV2
165+
title={language.t("settings.general.row.pinchZoom.title")}
166+
description={language.t("settings.general.row.pinchZoom.description")}
167+
>
168+
<div data-action="settings-pinch-zoom">
169+
<Switch checked={pinchZoom.latest} onChange={onPinchZoomChange} />
170+
</div>
171+
</SettingsRowV2>
172+
</SettingsListV2>
173+
</div>
174+
</Show>
175+
</div>
176+
</>
177+
)
178+
}

packages/app/src/components/settings-v2/dialog-settings-v2.tsx

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import { Icon } from "@opencode-ai/ui/icon"
55
import { useLanguage } from "@/context/language"
66
import { usePlatform } from "@/context/platform"
77
import { SettingsGeneralV2 } from "./general"
8+
import { SettingsAppearanceV2 } from "./appearance"
89
import { SettingsKeybinds } from "../settings-keybinds"
10+
import { SettingsNotificationsV2 } from "./notifications"
911
import { SettingsProvidersV2 } from "./providers"
1012
import { SettingsModelsV2 } from "./models"
11-
import "./settings-v2.css"
1213
import { SettingsServersV2 } from "./servers"
1314
import { SettingsWorkspacesV2 } from "./workspaces"
15+
import { SettingsProjectsV2 } from "./projects"
16+
import { SettingsExtensionsV2 } from "./extensions"
1417
import { useDialog } from "@opencode-ai/ui/context/dialog"
1518
import { useTabs } from "@/context/tabs"
1619
import { useServerSync } from "@/context/server-sync"
20+
import "./settings-v2.css"
1721

1822
export const DialogSettings: Component<{
1923
sessionID?: string
@@ -50,54 +54,76 @@ export const DialogSettings: Component<{
5054
>
5155
<TabsV2.List>
5256
<div class="flex flex-col justify-between h-full w-full">
53-
<div class="flex flex-col gap-3 w-full">
54-
<div class="flex flex-col gap-3">
55-
<div class="flex flex-col gap-1.5">
56-
<TabsV2.SectionTitle>{language.t("settings.section.desktop")}</TabsV2.SectionTitle>
57-
<div class="flex flex-col gap-1.5 w-full">
58-
<TabsV2.Trigger value="general">
59-
<Icon name="sliders" size="small" />
60-
{language.t("settings.tab.general")}
61-
</TabsV2.Trigger>
62-
<TabsV2.Trigger value="shortcuts">
63-
<Icon name="keyboard" size="small" />
64-
{language.t("settings.tab.shortcuts")}
65-
</TabsV2.Trigger>
66-
</div>
67-
</div>
57+
<div class="flex flex-col gap-4 w-full">
58+
{/* Group 1: Preferences */}
59+
<div class="flex flex-col gap-1 w-full">
60+
<TabsV2.Trigger value="general">
61+
<Icon name="sliders" />
62+
{language.t("settings.tab.preferences")}
63+
</TabsV2.Trigger>
64+
<TabsV2.Trigger value="appearance">
65+
<Icon name="appearance" />
66+
{language.t("settings.general.section.appearance")}
67+
</TabsV2.Trigger>
68+
<TabsV2.Trigger value="notifications">
69+
<Icon name="notifications" />
70+
{language.t("settings.tab.notifications")}
71+
</TabsV2.Trigger>
72+
<TabsV2.Trigger value="shortcuts">
73+
<Icon name="keyboard" />
74+
{language.t("settings.tab.shortcuts")}
75+
</TabsV2.Trigger>
76+
</div>
6877

69-
<div class="flex flex-col gap-1.5">
70-
<TabsV2.SectionTitle>{language.t("settings.section.server")}</TabsV2.SectionTitle>
71-
<div class="flex flex-col gap-1.5 w-full">
72-
<TabsV2.Trigger value="workspaces">
73-
<Icon name="workspace-isolated" size="small" />
74-
{language.t("settings.tab.workspaces")}
75-
</TabsV2.Trigger>
76-
<TabsV2.Trigger value="servers">
77-
<Icon name="server" size="small" />
78-
{language.t("status.popover.tab.servers")}
79-
</TabsV2.Trigger>
80-
<TabsV2.Trigger value="providers">
81-
<Icon name="providers" size="small" />
82-
{language.t("settings.providers.title")}
83-
</TabsV2.Trigger>
84-
<TabsV2.Trigger value="models">
85-
<Icon name="models" size="small" />
86-
{language.t("settings.models.title")}
87-
</TabsV2.Trigger>
88-
</div>
89-
</div>
78+
{/* Group 2: Environment & Workspaces */}
79+
<div class="flex flex-col gap-1 w-full">
80+
<TabsV2.Trigger value="workspaces">
81+
<Icon name="workspace-isolated" />
82+
{language.t("settings.tab.workspaces")}
83+
</TabsV2.Trigger>
84+
<TabsV2.Trigger value="servers">
85+
<Icon name="server" />
86+
{language.t("status.popover.tab.servers")}
87+
</TabsV2.Trigger>
88+
<TabsV2.Trigger value="projects">
89+
<Icon name="folder" />
90+
{language.t("settings.tab.projects")}
91+
</TabsV2.Trigger>
92+
</div>
93+
94+
{/* Group 3: Capabilities & Extensions */}
95+
<div class="flex flex-col gap-1 w-full">
96+
<TabsV2.Trigger value="providers">
97+
<Icon name="providers" />
98+
{language.t("settings.providers.title")}
99+
</TabsV2.Trigger>
100+
<TabsV2.Trigger value="models">
101+
<Icon name="models" />
102+
{language.t("settings.models.title")}
103+
</TabsV2.Trigger>
104+
<TabsV2.Trigger value="extensions">
105+
<Icon name="extensions" />
106+
{language.t("settings.tab.extensions")}
107+
</TabsV2.Trigger>
90108
</div>
91109
</div>
110+
92111
<div class="settings-v2-nav-footer">
93112
<span>{language.t("app.name.desktop")}</span>
94113
<span>v{platform.version}</span>
95114
</div>
96115
</div>
97116
</TabsV2.List>
117+
98118
<TabsV2.Content value="general" class="settings-v2-panel">
99119
<SettingsGeneralV2 sessionID={props.sessionID} />
100120
</TabsV2.Content>
121+
<TabsV2.Content value="appearance" class="settings-v2-panel">
122+
<SettingsAppearanceV2 />
123+
</TabsV2.Content>
124+
<TabsV2.Content value="notifications" class="settings-v2-panel">
125+
<SettingsNotificationsV2 />
126+
</TabsV2.Content>
101127
<TabsV2.Content value="shortcuts" class="settings-v2-panel">
102128
<SettingsKeybinds v2 />
103129
</TabsV2.Content>
@@ -107,12 +133,18 @@ export const DialogSettings: Component<{
107133
<TabsV2.Content value="servers" class="settings-v2-panel">
108134
<SettingsServersV2 />
109135
</TabsV2.Content>
136+
<TabsV2.Content value="projects" class="settings-v2-panel">
137+
<SettingsProjectsV2 />
138+
</TabsV2.Content>
110139
<TabsV2.Content value="providers" class="settings-v2-panel">
111140
<SettingsProvidersV2 directory={directory} onBack={showProviders} />
112141
</TabsV2.Content>
113142
<TabsV2.Content value="models" class="settings-v2-panel">
114143
<SettingsModelsV2 />
115144
</TabsV2.Content>
145+
<TabsV2.Content value="extensions" class="settings-v2-panel">
146+
<SettingsExtensionsV2 />
147+
</TabsV2.Content>
116148
</TabsV2>
117149
</Dialog>
118150
)

0 commit comments

Comments
 (0)