Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ This project uses semantic versioning for public releases. Use `MAJOR.MINOR.PATC
- `MINOR` changes add user-visible features and improvements.
- `PATCH` changes fix bugs, polish existing behavior, or make small internal improvements.

## [Unreleased]

### Improved

- Redesigned the Settings sidebar with an app icon and version header, selected-row highlighting, and hover feedback.

## [1.1.0] - 2026-06-03

### Added
Expand Down
78 changes: 55 additions & 23 deletions Sources/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -348,28 +348,43 @@ struct ProviderSettingsFields: View {
struct SettingsView: View {
@EnvironmentObject var appState: AppState

private var appVersion: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0"
}

var body: some View {
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 2) {
HStack(spacing: 8) {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 28, height: 28)
VStack(alignment: .leading, spacing: 0) {
Text(AppName.displayName)
.font(.system(size: 12, weight: .semibold))
Text("Version \(appVersion)")
.font(.system(size: 10))
.foregroundStyle(.secondary)
}
}
.padding(.horizontal, 10)
.padding(.top, 6)
.padding(.bottom, 12)

ForEach(SettingsTab.visibleCases) { tab in
Button {
SettingsSidebarRow(
title: tab.title,
icon: tab.icon,
isSelected: appState.selectedSettingsTab == tab
) {
appState.selectedSettingsTab = tab
} label: {
SettingsSidebarRow(title: tab.title, icon: tab.icon)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(appState.selectedSettingsTab == tab
? Color.accentColor.opacity(0.15)
: Color.clear)
)
}
.buttonStyle(.plain)
}

Spacer()
}
.padding(10)
.frame(width: 180)
.frame(width: 190)
.background(Color(nsColor: .windowBackgroundColor))

Divider()
Expand All @@ -396,22 +411,39 @@ struct SettingsView: View {
private struct SettingsSidebarRow: View {
let title: String
let icon: String
let isSelected: Bool
let action: () -> Void

@State private var isHovered = false

var body: some View {
HStack(spacing: 8) {
Image(systemName: icon)
.font(.system(size: 13, weight: .regular))
.frame(width: 16, height: 16, alignment: .center)
.foregroundStyle(.primary)
Button(action: action) {
HStack(spacing: 8) {
Image(systemName: icon)
.font(.system(size: 13, weight: .regular))
.frame(width: 16, height: 16, alignment: .center)
.foregroundStyle(isSelected ? Color.accentColor : Color.secondary)

Text(title)
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
Text(title)
.font(.system(size: 13, weight: isSelected ? .medium : .regular))
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(height: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 8)
.padding(.horizontal, 10)
.background(
RoundedRectangle(cornerRadius: 6, style: .continuous)
.fill(
isSelected
? Color.accentColor.opacity(0.15)
: Color.primary.opacity(isHovered ? 0.06 : 0)
)
)
.contentShape(Rectangle())
}
.frame(height: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 8)
.padding(.horizontal, 10)
.buttonStyle(.plain)
.onHover { isHovered = $0 }
}
}

Expand Down