refactor(settings): migrate controls to shared primitives

This commit is contained in:
xiaohan.yuan committed 2026-10-18 19:11:50 +08:00
1 parent 0284603847
commit fb7e2eb3da
2 files changed
+38 -60

No files matched your search

@@ -21,6 +21,9 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import type { GeneralSettingsView } from '@shared/types' import type { GeneralSettingsView } from '@shared/types'
import { Button } from '../ui/Button'
import { Input } from '../ui/Input'
import { Switch } from '../ui/Switch'
import { IconTaskOpenFolder } from './task-menu-icons' import { IconTaskOpenFolder } from './task-menu-icons'
import { IconNotificationAuthorize, IconSelectCaret } from './general-settings-icons' import { IconNotificationAuthorize, IconSelectCaret } from './general-settings-icons'
import './source-general-settings.css' import './source-general-settings.css'
@@ -110,40 +113,6 @@ function Section({ title, desc, action, body, stacked, className }: SectionProps
) )
} }
function Switch({ checked, ariaLabel, disabled, onChange }: {
checked: boolean
ariaLabel: string
disabled?: boolean
onChange(checked: boolean): void
}): React.JSX.Element {
return (
<div className="general-settings-panel__switch-wrap">
<label className="wb-switch wb-switch--large">
<span className="wb-switch__control">
<input className="wb-switch__input" type="checkbox" role="switch" aria-label={ariaLabel} checked={checked}
disabled={disabled} onChange={(event) => onChange(event.target.checked)} />
<span className="wb-switch__track"><span className="wb-switch__thumb" /></span>
</span>
</label>
</div>
)
}
/** 未接入的开关:保持原版开/关外观与尺寸,但不可交互。 */
function UnavailableSwitch({ checked, ariaLabel }: { checked: boolean; ariaLabel: string }): React.JSX.Element {
return (
<div className="general-settings-panel__switch-wrap" data-settings-unavailable="true" aria-disabled="true" title={UNAVAILABLE_HINT}>
<label className="wb-switch wb-switch--large" aria-disabled="true">
<span className="wb-switch__control">
<input className="wb-switch__input" type="checkbox" role="switch" aria-label={ariaLabel} checked={checked}
readOnly tabIndex={-1} aria-disabled="true" />
<span className="wb-switch__track"><span className="wb-switch__thumb" /></span>
</span>
</label>
</div>
)
}
/** 未接入的下拉:原版 120×32 闭合态(value + caret),不可展开。 */ /** 未接入的下拉:原版 120×32 闭合态(value + caret),不可展开。 */
function UnavailableSelect({ value, className, ariaLabel }: { value: string; className?: string; ariaLabel: string }): React.JSX.Element { function UnavailableSelect({ value, className, ariaLabel }: { value: string; className?: string; ariaLabel: string }): React.JSX.Element {
return ( return (
@@ -239,21 +208,27 @@ export default function GeneralSettingsPanel(): React.JSX.Element {
<Section title="允许锁屏运行" desc="选择锁屏后的运行方式,保障远程控制与后台 Agent 任务持续执行。" <Section title="允许锁屏运行" desc="选择锁屏后的运行方式,保障远程控制与后台 Agent 任务持续执行。"
action={<UnavailableSelect value="关闭" ariaLabel="允许锁屏运行" className="power-save-blocker-settings__select" />} /> action={<UnavailableSelect value="关闭" ariaLabel="允许锁屏运行" className="power-save-blocker-settings__select" />} />
<Section title="运行时防止系统休眠" desc="在 YCode 运行任务时,让电脑保持唤醒状态。" <Section title="运行时防止系统休眠" desc="在 YCode 运行任务时,让电脑保持唤醒状态。"
action={<Switch checked={view?.preventSleep ?? true} ariaLabel="运行时防止系统休眠" disabled={busy || !view} action={<Switch checked={view?.preventSleep ?? true} aria-label="运行时防止系统休眠" disabled={busy || !view} size="lg"
onChange={(checked) => update({ preventSleep: checked })} />} /> onCheckedChange={(checked) => update({ preventSleep: checked })} />} />
<Section title="开机自动启动" desc="开启后 YCode 会在你登录电脑后自动启动。" <Section title="开机自动启动" desc="开启后 YCode 会在你登录电脑后自动启动。"
action={<Switch checked={view?.autoLaunch ?? false} ariaLabel="开机自动启动" disabled={busy || !view} action={<Switch checked={view?.autoLaunch ?? false} aria-label="开机自动启动" disabled={busy || !view} size="lg"
onChange={(checked) => update({ autoLaunch: checked })} />} /> onCheckedChange={(checked) => update({ autoLaunch: checked })} />} />
<Section title="网络代理" desc="网络异常时可切换连接方式,修改后立即生效" stacked <Section title="网络代理" desc="网络异常时可切换连接方式,修改后立即生效" stacked
className="general-settings-panel__section--proxy" className="general-settings-panel__section--proxy"
action={<UnavailableSelect value="直接连接" ariaLabel="网络代理" />} action={<UnavailableSelect value="直接连接" ariaLabel="网络代理" />}
body={<div className="proxy-section" />} /> body={<div className="proxy-section" />} />
<Section title="自动安装可信技能" desc="安全检测通过后自动安装,高风险项始终要求手动确认。" <Section title="自动安装可信技能" desc="安全检测通过后自动安装,高风险项始终要求手动确认。"
action={<UnavailableSwitch checked={false} ariaLabel="自动安装可信技能" />} /> action={<div className="general-settings-panel__switch-wrap" data-settings-unavailable="true" aria-disabled="true" title={UNAVAILABLE_HINT}>
<Switch checked={false} readOnly aria-label="自动安装可信技能" size="lg" />
</div>} />
<Section title="技能自动更新" desc="开启后将自动更新已安装的技能为最新版本,不会更新你在 YCode 中编辑过的技能" <Section title="技能自动更新" desc="开启后将自动更新已安装的技能为最新版本,不会更新你在 YCode 中编辑过的技能"
action={<UnavailableSwitch checked ariaLabel="技能自动更新" />} /> action={<div className="general-settings-panel__switch-wrap" data-settings-unavailable="true" aria-disabled="true" title={UNAVAILABLE_HINT}>
<Switch checked readOnly aria-label="技能自动更新" size="lg" />
</div>} />
<Section title="套件自动更新" desc="开启后将自动更新已安装的套件为最新版本。" <Section title="套件自动更新" desc="开启后将自动更新已安装的套件为最新版本。"
action={<UnavailableSwitch checked ariaLabel="套件自动更新" />} /> action={<div className="general-settings-panel__switch-wrap" data-settings-unavailable="true" aria-disabled="true" title={UNAVAILABLE_HINT}>
<Switch checked readOnly aria-label="套件自动更新" size="lg" />
</div>} />
</div> </div>
</div> </div>
@@ -263,11 +238,10 @@ export default function GeneralSettingsPanel(): React.JSX.Element {
<div className="wb-storage__cache"> <div className="wb-storage__cache">
<div className="wb-storage__card-title-row"> <div className="wb-storage__card-title-row">
<div className="wb-storage__card-title">系统缓存目录</div> <div className="wb-storage__card-title">系统缓存目录</div>
<button type="button" className="wb-button wb-button--ghost wb-button--medium wb-storage__open-folder" <Button variant="ghost" size="md" iconLeft={<IconTaskOpenFolder />}
onClick={() => openFolder('data')}> className="wb-storage__open-folder" onClick={() => openFolder('data')}>
<span className="wb-button__icon wb-button__icon--left"><IconTaskOpenFolder className="wb-icon" /></span> 打开目录
<span className="wb-button__content">打开目录</span> </Button>
</button>
</div> </div>
<div className="wb-storage__path-row"> <div className="wb-storage__path-row">
<span className="wb-storage__path-text">{view?.dataDir ?? '—'} · 存放对话记录、运行缓存与临时文件。</span> <span className="wb-storage__path-text">{view?.dataDir ?? '—'} · 存放对话记录、运行缓存与临时文件。</span>
@@ -310,12 +284,9 @@ export default function GeneralSettingsPanel(): React.JSX.Element {
</div> </div>
<p className="general-settings-panel__workspace-path-desc">新建任务、工作空间时将自动存放在该路径下;修改后不影响已有数据。</p> <p className="general-settings-panel__workspace-path-desc">新建任务、工作空间时将自动存放在该路径下;修改后不影响已有数据。</p>
<div className="general-settings-panel__path-row"> <div className="general-settings-panel__path-row">
<input className="wb-input wb-input--medium wb-input--filled general-settings-panel__path-input" <Input size="md" filled readOnly wrapperClassName="general-settings-panel__path-input"
aria-label="默认工作空间存储路径" value={view?.workspaceRoot ?? ''} readOnly /> aria-label="默认工作空间存储路径" value={view?.workspaceRoot ?? ''} />
<button type="button" className="wb-button wb-button--secondary wb-button--medium" disabled={busy || !view} <Button variant="secondary" size="md" disabled={busy || !view} onClick={changeWorkspaceRoot}>更改</Button>
onClick={changeWorkspaceRoot}>
<span className="wb-button__content">更改</span>
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -325,15 +296,14 @@ export default function GeneralSettingsPanel(): React.JSX.Element {
<p className="general-settings-panel__group-label">通知</p> <p className="general-settings-panel__group-label">通知</p>
<div className="general-settings-panel__group-card"> <div className="general-settings-panel__group-card">
<Section title="桌面通知" desc="任务完成或有新消息时,将会通过系统弹窗提醒你。" <Section title="桌面通知" desc="任务完成或有新消息时,将会通过系统弹窗提醒你。"
action={<button type="button" className="wb-button wb-button--secondary wb-button--medium" action={<Button variant="secondary" size="md" iconRight={<IconNotificationAuthorize />}
onClick={openNotificationSettings}> onClick={openNotificationSettings}>前往系统设置</Button>} />
<span className="wb-button__content">前往系统设置</span>
<span className="wb-button__icon wb-button__icon--right"><IconNotificationAuthorize /></span>
</button>} />
<Section title="通知提示音" desc="收到桌面或应用内通知时播放的提示音。" <Section title="通知提示音" desc="收到桌面或应用内通知时播放的提示音。"
action={<UnavailableSelect value="无音效" ariaLabel="通知提示音" />} /> action={<UnavailableSelect value="无音效" ariaLabel="通知提示音" />} />
<Section title="定时任务产物通知" desc="定时任务的产物生成后,将会通过系统弹窗提醒你。" <Section title="定时任务产物通知" desc="定时任务的产物生成后,将会通过系统弹窗提醒你。"
action={<UnavailableSwitch checked ariaLabel="定时任务产物通知" />} /> action={<div className="general-settings-panel__switch-wrap" data-settings-unavailable="true" aria-disabled="true" title={UNAVAILABLE_HINT}>
<Switch checked readOnly aria-label="定时任务产物通知" size="lg" />
</div>} />
</div> </div>
</div> </div>
@@ -346,7 +316,9 @@ export default function GeneralSettingsPanel(): React.JSX.Element {
{' '}<a className="general-settings-panel__inline-link" href="#none" aria-disabled="true" title={UNAVAILABLE_HINT} {' '}<a className="general-settings-panel__inline-link" href="#none" aria-disabled="true" title={UNAVAILABLE_HINT}
onClick={(event) => event.preventDefault()}>了解更多</a> onClick={(event) => event.preventDefault()}>了解更多</a>
</>} </>}
action={<UnavailableSwitch checked ariaLabel="体验优化计划" />} /> action={<div className="general-settings-panel__switch-wrap" data-settings-unavailable="true" aria-disabled="true" title={UNAVAILABLE_HINT}>
<Switch checked readOnly aria-label="体验优化计划" size="lg" />
</div>} />
</div> </div>
</div> </div>
</div> </div>
@@ -3,6 +3,12 @@ import { useSettingsStore } from '../stores/settings'
import SourceSettingsNavigation from './SourceSettingsNavigation' import SourceSettingsNavigation from './SourceSettingsNavigation'
import { useSharedTheme } from '../useSharedTheme' import { useSharedTheme } from '../useSharedTheme'
import { SOURCE_WINDOWS_SETTINGS_TABS } from './SourceSettingsNavigation' import { SOURCE_WINDOWS_SETTINGS_TABS } from './SourceSettingsNavigation'
import { Button } from '../ui/Button'
// 统一组件样式必须在恢复样式之前加载:恢复样式是页面作用域的覆盖层,
// 同权重时后写者胜,顺序反转会让 `.yc-*` 默认值盖掉 `.general-settings-panel__*` 的页面覆盖。
// `../ui/Button` 已把 button.css 提前带入,这里补上其余两张组件表。
import '../ui/input.css'
import '../ui/switch.css'
// Shared UI-kit controls are also used by direct links to Memory/Agents/About. // Shared UI-kit controls are also used by direct links to Memory/Agents/About.
import './source-select/source-select-recovered.css' import './source-select/source-select-recovered.css'
import './source-select/source-select.css' import './source-select/source-select.css'
@@ -77,7 +83,7 @@ export default function SettingsDialog({ theme, onThemeChange, onClose, nativeWi
{error && <p role="alert">{error}</p>} {error && <p role="alert">{error}</p>}
{section === 'settings' && <GeneralSettingsPanel />} {section === 'settings' && <GeneralSettingsPanel />}
{section === 'model' && <ModelSettingsPanel models={settings.models} createRequest={createRequest} onCreateHandled={() => setCreateRequest(0)} onSave={settings.saveModel} onDelete={settings.deleteModel} onTest={(input) => window.ycode.models.test(input)} />} {section === 'model' && <ModelSettingsPanel models={settings.models} createRequest={createRequest} onCreateHandled={() => setCreateRequest(0)} onSave={settings.saveModel} onDelete={settings.deleteModel} onTest={(input) => window.ycode.models.test(input)} />}
{section === 'workspace' && <div className="settings-window-panel"><h2>工作空间</h2>{settings.workspaces.map((space) => <button className="btn" key={space.id} title={space.root} onClick={() => void window.ycode.workspace.open(space.id).catch((error) => setError(String(error.message ?? error)))}>{space.name}</button>)}<button className="btn" onClick={() => void settings.attachWorkspace().catch((error) => setError(String(error.message ?? error)))}>打开已有文件夹</button></div>} {section === 'workspace' && <div className="settings-window-panel"><h2>工作空间</h2>{settings.workspaces.map((space) => <Button variant="secondary" size="md" key={space.id} title={space.root} onClick={() => void window.ycode.workspace.open(space.id).catch((error) => setError(String(error.message ?? error)))}>{space.name}</Button>)}<Button variant="secondary" size="md" onClick={() => void settings.attachWorkspace().catch((error) => setError(String(error.message ?? error)))}>打开已有文件夹</Button></div>}
{section === 'appearance' && <AppearanceSettingsPanel theme={theme} onThemeChange={onThemeChange} />} {section === 'appearance' && <AppearanceSettingsPanel theme={theme} onThemeChange={onThemeChange} />}
{section === 'keyboardShortcuts' && <ShortcutsSettingsPanel />} {section === 'keyboardShortcuts' && <ShortcutsSettingsPanel />}
{section === 'personalization' && <PersonalizationSettingsPanel />} {section === 'personalization' && <PersonalizationSettingsPanel />}