feat(ui): align settings with SwiftUI

This commit is contained in:
NimBold
2026-06-14 10:04:53 +03:30
parent 0e588fa360
commit 58cbd0567d
2 changed files with 185 additions and 71 deletions
+105 -54
View File
@@ -196,13 +196,13 @@ export default function SettingsView() {
type="button"
data-active={active}
onClick={() => settings.setActiveSettingsTab(type)}
className={`settings-tab-button flex min-w-0 flex-1 flex-col items-center justify-center rounded-lg px-1 py-2 text-center cursor-default transition-colors ${
className={`settings-tab-button flex min-w-0 flex-1 flex-col items-center justify-center px-1 text-center cursor-default ${
active
? 'text-text-primary'
: 'text-text-secondary hover:bg-item-hover hover:text-text-primary'
? 'text-white'
: 'text-text-primary hover:bg-item-hover'
}`}
>
<Icon size={15} strokeWidth={active ? 2.2 : 1.8} className={active ? 'text-accent' : ''} />
<Icon size={16} strokeWidth={2} />
<span className="settings-tab-label mt-1 w-full whitespace-nowrap font-medium">{label}</span>
</button>
);
@@ -221,7 +221,7 @@ export default function SettingsView() {
{/* SwiftUI SettingsPaneContainer-style horizontal tab strip */}
<div className="settings-toolbar">
<div className="settings-tab-strip mx-6 mb-3 flex items-stretch gap-1 rounded-xl p-1">
<div className="settings-tab-strip flex items-stretch gap-1">
{settingsTabs.map(tab => (
<TabButton key={tab.type} {...tab} />
))}
@@ -230,28 +230,19 @@ export default function SettingsView() {
{/* Content Area */}
<div className="settings-scroll flex-1 overflow-y-auto">
<div className="w-full p-7">
<h1 className="mb-6 text-[22px] font-semibold tracking-tight text-text-primary">{activeTabLabel}</h1>
<div className="settings-content max-w-[760px]">
<div className="settings-content-shell w-full">
<h1 className="settings-title text-text-primary">{activeTabLabel}</h1>
<div className="settings-content max-w-[720px]">
{/* Downloads Pane */}
{activeTab === 'downloads' && (
<div className="settings-pane max-w-[760px]">
<div className="settings-pane max-w-[720px]">
<div className="mac-settings-group">
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Parallel downloads</span>
<div className="flex items-center gap-3">
<span className="text-text-muted text-[13px] w-6 text-right">{settings.maxConcurrentDownloads}</span>
<input
type="range" min="1" max="12"
value={settings.maxConcurrentDownloads}
onChange={(e) => settings.setMaxConcurrentDownloads(Number(e.target.value))}
className="accent-accent w-24"
/>
<div className="settings-row-label">
<span>Default connections:</span>
<small>For new downloads</small>
</div>
</div>
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Default connections</span>
<input
type="number" min="1" max="16"
value={settings.perServerConnections}
@@ -260,17 +251,38 @@ export default function SettingsView() {
/>
</div>
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Global speed limit</span>
<div className="settings-row-label">
<span>Parallel downloads:</span>
<small>Max simultaneous active files</small>
</div>
<input
type="number" min="1" max="12"
value={settings.maxConcurrentDownloads}
onChange={(e) => settings.setMaxConcurrentDownloads(Number(e.target.value))}
className="app-control w-16 text-center"
/>
</div>
<div className="mac-settings-row">
<div className="settings-row-label">
<span>Global speed limit:</span>
<small>0 = unlimited speed</small>
</div>
<div className="flex items-center gap-2">
<input
type="text"
value={settings.globalSpeedLimit}
onChange={(e) => settings.setGlobalSpeedLimit(e.target.value)}
placeholder="0"
className="app-control w-24 text-center font-mono"
className="app-control w-20 text-right font-mono px-2"
/>
<span className="text-[12px] text-text-muted">KiB/s</span>
</div>
</div>
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Automatic retries</span>
<div className="settings-row-label">
<span>Automatic retries:</span>
<small>If a connection fails</small>
</div>
<input
type="number" min="0" max="10"
value={settings.maxAutomaticRetries}
@@ -282,7 +294,10 @@ export default function SettingsView() {
<div className="mac-settings-group">
<label className="mac-settings-row cursor-default">
<span className="text-[13px] text-text-primary">Show completion notifications</span>
<div className="settings-row-label">
<span>Show notification when download completes</span>
<small>Alerts you in Notification Center</small>
</div>
<input
type="checkbox"
checked={settings.showNotifications}
@@ -291,7 +306,7 @@ export default function SettingsView() {
/>
</label>
<label className="mac-settings-row cursor-default" style={{ opacity: settings.showNotifications ? 1 : 0.5 }}>
<span className="text-[13px] text-text-primary">Play completion sound</span>
<span className="text-[13px] text-text-primary">Play sound when download completes</span>
<input
type="checkbox"
checked={settings.playCompletionSound}
@@ -306,24 +321,35 @@ export default function SettingsView() {
{/* Look & Feel Pane */}
{activeTab === 'lookandfeel' && (
<div className="settings-pane max-w-[760px]">
<div className="settings-pane max-w-[720px]">
<h2 className="settings-section-title">App Theme</h2>
<div className="mac-settings-group">
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">App Theme</span>
<select
value={settings.theme}
onChange={(e) => settings.setTheme(e.target.value as any)}
className="app-control w-40"
>
<option value="system">System Default</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="dracula">Dracula</option>
<option value="nord">Nord</option>
</select>
<div className="mac-settings-row items-start">
<span className="text-[13px] text-text-primary pt-0.5">Theme</span>
<div className="settings-radio-group">
{[
['system', 'System Default'],
['light', 'Light'],
['dark', 'Dark'],
['dracula', 'Dracula'],
['nord', 'Nord'],
].map(([value, label]) => (
<label key={value}>
<input
type="radio"
name="app-theme"
checked={settings.theme === value}
onChange={() => settings.setTheme(value as typeof settings.theme)}
/>
<span>{label}</span>
</label>
))}
</div>
</div>
<p className="settings-group-footer">Select a color palette for the app's user interface.</p>
</div>
<h2 className="settings-section-title">Display</h2>
<div className="mac-settings-group">
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Font Size</span>
@@ -351,9 +377,13 @@ export default function SettingsView() {
</div>
</div>
<h2 className="settings-section-title">macOS Integration</h2>
<div className="mac-settings-group">
<label className="mac-settings-row cursor-default">
<span className="text-[13px] text-text-primary">Show badge on Dock icon</span>
<div className="settings-row-label">
<span>Show badge on Dock icon</span>
<small>Displays the number of active downloads on the Firelink Dock icon.</small>
</div>
<input
type="checkbox"
checked={settings.showDockBadge}
@@ -362,7 +392,10 @@ export default function SettingsView() {
/>
</label>
<label className="mac-settings-row cursor-default">
<span className="text-[13px] text-text-primary">Show menu bar icon</span>
<div className="settings-row-label">
<span>Show menu bar icon</span>
<small>Provides quick access to downloads and queues from the macOS menu bar.</small>
</div>
<input
type="checkbox"
checked={settings.showMenuBarIcon}
@@ -376,19 +409,28 @@ export default function SettingsView() {
{/* Network Pane */}
{activeTab === 'network' && (
<div className="settings-pane max-w-[760px]">
<div className="settings-pane max-w-[720px]">
<h2 className="settings-section-title">Proxy</h2>
<div className="mac-settings-group">
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Proxy Mode</span>
<select
value={settings.proxyMode}
onChange={(e) => settings.setProxyMode(e.target.value as any)}
className="app-control w-40"
>
<option value="none">No proxy</option>
<option value="system">Use system proxy</option>
<option value="custom">Set custom proxy</option>
</select>
<div className="mac-settings-row items-start">
<span className="text-[13px] text-text-primary pt-0.5">Mode</span>
<div className="settings-radio-group">
{[
['none', 'No Proxy'],
['system', 'Use System Proxy'],
['custom', 'Custom Proxy'],
].map(([value, label]) => (
<label key={value}>
<input
type="radio"
name="proxy-mode"
checked={settings.proxyMode === value}
onChange={() => settings.setProxyMode(value as typeof settings.proxyMode)}
/>
<span>{label}</span>
</label>
))}
</div>
</div>
{settings.proxyMode === 'custom' && (
<>
@@ -413,8 +455,16 @@ export default function SettingsView() {
</div>
</>
)}
<p className="settings-group-footer">
{settings.proxyMode === 'none' && 'Downloads ignore configured proxies.'}
{settings.proxyMode === 'system' && 'Downloads use the matching macOS system proxy when one is configured.'}
{settings.proxyMode === 'custom' && (settings.proxyHost
? `Downloads use http://${settings.proxyHost}:${settings.proxyPort}.`
: 'Enter a proxy host and port to enable the custom proxy.')}
</p>
</div>
<h2 className="settings-section-title">Identity</h2>
<div className="mac-settings-group">
<div className="mac-settings-row">
<span className="text-[13px] text-text-primary">Custom User Agent</span>
@@ -426,6 +476,7 @@ export default function SettingsView() {
className="app-control flex-1 ml-4 font-mono text-[11px]"
/>
</div>
<p className="settings-group-footer">Spoofs the browser User-Agent to bypass download restrictions. Leave blank for default.</p>
</div>
</div>
)}
+80 -17
View File
@@ -487,40 +487,55 @@
}
.settings-toolbar {
background: hsl(var(--sidebar-bg));
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid hsl(var(--border-color));
}
.settings-tab-strip {
background: hsl(var(--bg-input));
border: 1px solid hsl(var(--border-color));
border-radius: 8px;
padding: 2px;
padding: 16px 32px;
}
.settings-tab-button {
border-radius: 6px;
min-height: 64px;
border-radius: 8px;
transition: none;
}
.settings-tab-button[data-active="true"] {
background: hsl(var(--surface-raised));
box-shadow: 0 1px 2px hsl(0 0% 0% / 0.1);
color: var(--color-text-primary);
background: hsl(var(--accent-color));
box-shadow: none;
color: white;
}
.settings-pane {
padding: 24px;
padding: 0;
background: transparent;
}
.settings-content-shell {
padding: 32px;
}
.settings-title {
margin: 0 0 24px;
font-size: 28px;
line-height: 1.15;
font-weight: 650;
letter-spacing: -0.025em;
}
.settings-section-title {
margin: 0 0 7px 12px;
font-size: 12px;
line-height: 1.2;
font-weight: 600;
color: hsl(var(--text-secondary));
}
.mac-settings-group {
background: hsl(var(--bg-modal));
border-radius: 10px;
border-radius: 9px;
border: 1px solid hsl(var(--border-color));
box-shadow: 0 1px 2px hsl(var(--shadow-color));
box-shadow: none;
overflow: hidden;
margin-bottom: 24px;
}
@@ -529,7 +544,8 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
min-height: 44px;
padding: 9px 14px;
border-bottom: 1px solid hsl(var(--border-color));
}
@@ -537,6 +553,49 @@
border-bottom: none;
}
.settings-row-label {
display: flex;
min-width: 0;
flex-direction: column;
gap: 2px;
font-size: 13px;
color: hsl(var(--text-primary));
}
.settings-row-label small {
font-size: 11px;
line-height: 1.25;
font-weight: 400;
color: hsl(var(--text-muted));
}
.settings-radio-group {
display: flex;
min-width: 180px;
flex-direction: column;
gap: 7px;
font-size: 13px;
}
.settings-radio-group label {
display: flex;
align-items: center;
gap: 7px;
}
.settings-radio-group input {
accent-color: hsl(var(--accent-color));
}
.settings-group-footer {
margin: 0;
padding: 9px 14px;
border-top: 1px solid hsl(var(--border-color));
font-size: 11px;
line-height: 1.35;
color: hsl(var(--text-muted));
}
/* macOS Switch */
.mac-switch {
appearance: none;
@@ -904,15 +963,19 @@
}
.settings-tab-label {
font-size: 13px;
font-size: 11px;
font-weight: 500;
}
@media (max-width: 900px) {
.settings-tab-label {
font-size: 11px;
font-size: 9px;
letter-spacing: -0.01em;
}
.settings-tab-strip {
padding-inline: 20px;
}
}
@keyframes fade-in {