diff --git a/CHANGELOG.md b/CHANGELOG.md index a67e76644..fa243322a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- HIG polish (phase 5): nine hero icons (empty / success / error / Pro feature gate states across Onboarding, Feedback, Export Success, Import Success, Import Error, Query Success, Main editor empty state) move from hardcoded `.system(size: 40-64)` to a Dynamic-Type-aware combination of `.font(.largeTitle).imageScale(.large).symbolRenderingMode(.hierarchical)`, so they scale with the user's accessibility text-size setting and gain the canonical SF Symbols depth treatment. Five `.plain`-button-as-link callsites (Fetch All in the status bar, Show All / Hide All in the column visibility popover, Skip in onboarding, Close in the feedback success state) move to `.buttonStyle(.link)` or `.borderless`. Spacing sweep across 8 files normalises 5pt-grid magic numbers (`spacing: 5`, `spacing: 14`, `padding(.horizontal, 5)`) onto Apple's 8pt grid (`spacing: 4`, `spacing: 12`, `padding(.horizontal, 6)`). The Connection Form's Cancel button gains `.keyboardShortcut(.cancelAction)` so Esc dismisses correctly. - HIG list & window cleanup (phase 4): three small refactors that align inline list affordances with the rest of the codebase. `SlowQueryListView` (the collapsible "Slow Queries" panel in the Server Dashboard) drops a hand-rolled `Button { chevron }` + `ScrollView` + `LazyVStack` and uses a real `DisclosureGroup` + `List`, so VoiceOver gets the disclosure semantics and keyboard handling for free, and the orange-on-red error pill in its header switches to the warning-triangle convention introduced in phase 3. `ColumnVisibilityPopover` (the column show/hide popover) replaces its `ScrollView` + `LazyVStack` with a native `List`, gaining row hover, separators, and the standard list keyboard-navigation it was missing; the "Show All" / "Hide All" buttons in its header switch from `.plain` + manual accent foreground to `.buttonStyle(.link)`. `JSONViewerWindowController` deletes its hand-coded UserDefaults size persistence and uses the `NSWindow.applyAutosaveName` helper that every other imperative window in the project already uses; window position is now remembered too, not just size. - HIG pattern refactors (phase 3): five callsites migrate off bespoke implementations onto native primitives. `ResultTabBar` (the `.plain`-button strip in the results pane) gains hover states, accent-tint selection, and a `.bar` material background that matches macOS Sequoia inline tab patterns. `ConnectionSwitcherPopover` drops its manual `selectedIndex: Int` plus per-row `isHighlighted` color flipping and uses native `List(selection:)` for keyboard navigation, focus chrome, and scroll-into-view. The Welcome window's `+` and "new group" buttons replace a custom `@State isHovering` background with `.buttonStyle(.bordered).controlSize(.large)`. Eight inline form validation banners (URL parsing, plugin install, pgpass permissions, jump-host test, license activation, etc.) move from raw `.foregroundStyle(.systemRed)` text to a semantic `Label("...", systemImage: "exclamationmark.triangle.fill").foregroundStyle(.systemOrange)` pattern matching Apple's form-warning convention. Four sheets (Create Database, Create Tag, Export Options, Pagination Settings popover) are rebuilt on `Form { Section { ... } }.formStyle(.grouped)` with `LabeledContent` rows instead of hand-laid `VStack` + caption-styled labels + `roundedBorder` TextFields. - HIG component extraction (phase 2): introduces three reusable components — `TypeBadge`, `ProBadge`, and `ColorPaletteView` — replacing five duplicated open-coded variants across the UI. The column-type pill (previously hand-written in three places with hardcoded 9pt fonts and no accessibility labels) becomes one Dynamic-Type-aware `TypeBadge` that announces "Type: VARCHAR" to VoiceOver. The "PRO" pill standardises on a Capsule shape with `.caption2.bold()` and a "Pro feature" accessibility label. The connection / tag / group color pickers (three near-duplicate `HStack`-of-circles) collapse into one `ColorPaletteView` with `.compact` and `.regular` size variants. The toolbar `TagBadgeView` switches to a saturated tint background with white text for proper Increase Contrast support, replacing the same-hue text + 0.2-opacity-fill pattern that failed AA contrast for several preset tags. diff --git a/TablePro/Views/AIChat/AIChatMessageView.swift b/TablePro/Views/AIChat/AIChatMessageView.swift index 1519affed..2f074876f 100644 --- a/TablePro/Views/AIChat/AIChatMessageView.swift +++ b/TablePro/Views/AIChat/AIChatMessageView.swift @@ -196,7 +196,7 @@ private struct TypingIndicatorView: View { @State private var animating = false var body: some View { - HStack(spacing: 5) { + HStack(spacing: 4) { ForEach(0..<3, id: \.self) { index in Circle() .fill(Color(nsColor: .tertiaryLabelColor)) diff --git a/TablePro/Views/Components/ProFeatureGate.swift b/TablePro/Views/Components/ProFeatureGate.swift index 5a4067b9f..cc9058969 100644 --- a/TablePro/Views/Components/ProFeatureGate.swift +++ b/TablePro/Views/Components/ProFeatureGate.swift @@ -40,7 +40,9 @@ struct ProFeatureGateModifier: ViewModifier { VStack(spacing: 12) { Image(systemName: feature.systemImage) - .font(.system(size: 40)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(.secondary) switch access { diff --git a/TablePro/Views/Components/TypeBadge.swift b/TablePro/Views/Components/TypeBadge.swift index dff0175ed..3b03112c3 100644 --- a/TablePro/Views/Components/TypeBadge.swift +++ b/TablePro/Views/Components/TypeBadge.swift @@ -18,7 +18,7 @@ struct TypeBadge: View { Text(label) .font(.caption2.weight(.medium)) .foregroundStyle(.tertiary) - .padding(.horizontal, 5) + .padding(.horizontal, 6) .padding(.vertical, 1) .background(.quaternary, in: Capsule()) .accessibilityLabel(Text("Type: \(accessibilityDescription ?? label)")) diff --git a/TablePro/Views/Connection/ConnectionFormView+Footer.swift b/TablePro/Views/Connection/ConnectionFormView+Footer.swift index 4d2ee3d43..486c39c2c 100644 --- a/TablePro/Views/Connection/ConnectionFormView+Footer.swift +++ b/TablePro/Views/Connection/ConnectionFormView+Footer.swift @@ -54,6 +54,7 @@ extension ConnectionFormView { Button("Cancel") { dismiss() } + .keyboardShortcut(.cancelAction) if isNew { Button(String(localized: "Save")) { diff --git a/TablePro/Views/Connection/OnboardingContentView.swift b/TablePro/Views/Connection/OnboardingContentView.swift index 7c36ad545..2187eb8d8 100644 --- a/TablePro/Views/Connection/OnboardingContentView.swift +++ b/TablePro/Views/Connection/OnboardingContentView.swift @@ -150,7 +150,9 @@ struct OnboardingContentView: View { private var getStartedPage: some View { VStack(spacing: 16) { Image(systemName: "checkmark.circle.fill") - .font(.system(size: 48)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemGreen)) Text("You're all set!") @@ -170,8 +172,7 @@ struct OnboardingContentView: View { Button("Skip") { completeOnboarding() } - .buttonStyle(.plain) - .foregroundStyle(.secondary) + .buttonStyle(.link) .opacity(currentPage == 2 ? 0 : 1) .frame(minWidth: 110, alignment: .leading) diff --git a/TablePro/Views/Connection/WelcomeLeftPanel.swift b/TablePro/Views/Connection/WelcomeLeftPanel.swift index aedf0c906..bffe0275e 100644 --- a/TablePro/Views/Connection/WelcomeLeftPanel.swift +++ b/TablePro/Views/Connection/WelcomeLeftPanel.swift @@ -130,7 +130,7 @@ struct KeyboardHint: View { HStack(spacing: 4) { Text(keys) .font(.system(.caption, design: .monospaced)) - .padding(.horizontal, 5) + .padding(.horizontal, 6) .padding(.vertical, 2) .background( RoundedRectangle(cornerRadius: 3) diff --git a/TablePro/Views/Editor/QuerySuccessView.swift b/TablePro/Views/Editor/QuerySuccessView.swift index 37d885f91..3fcca5cc1 100644 --- a/TablePro/Views/Editor/QuerySuccessView.swift +++ b/TablePro/Views/Editor/QuerySuccessView.swift @@ -19,7 +19,9 @@ struct QuerySuccessView: View { // Success icon Image(systemName: "checkmark.circle.fill") - .font(.system(size: 64)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemGreen)) // Success message diff --git a/TablePro/Views/Export/ExportSuccessView.swift b/TablePro/Views/Export/ExportSuccessView.swift index 3936cf6eb..5f13fc6cf 100644 --- a/TablePro/Views/Export/ExportSuccessView.swift +++ b/TablePro/Views/Export/ExportSuccessView.swift @@ -24,7 +24,9 @@ struct ExportSuccessView: View { VStack(spacing: 20) { // Success icon Image(systemName: "checkmark.circle.fill") - .font(.system(size: 48)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemGreen)) // Title and message diff --git a/TablePro/Views/Feedback/FeedbackView.swift b/TablePro/Views/Feedback/FeedbackView.swift index ec45bd383..34cf9de15 100644 --- a/TablePro/Views/Feedback/FeedbackView.swift +++ b/TablePro/Views/Feedback/FeedbackView.swift @@ -277,7 +277,9 @@ struct FeedbackView: View { Spacer() Image(systemName: "checkmark.circle.fill") - .font(.system(size: 44)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemGreen)) Text("Feedback submitted!") @@ -303,8 +305,7 @@ struct FeedbackView: View { NSApp.keyWindow?.close() } .font(.subheadline) - .buttonStyle(.plain) - .foregroundStyle(.secondary) + .buttonStyle(.borderless) } Spacer() diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index 00cb3654a..2ea685f8f 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -227,7 +227,7 @@ struct ImportDialog: View { } private var optionsView: some View { - VStack(alignment: .leading, spacing: 14) { + VStack(alignment: .leading, spacing: 12) { Text("Options") .font(.callout.weight(.semibold)) .foregroundStyle(.primary) diff --git a/TablePro/Views/Import/ImportErrorView.swift b/TablePro/Views/Import/ImportErrorView.swift index 497442ce6..fce331248 100644 --- a/TablePro/Views/Import/ImportErrorView.swift +++ b/TablePro/Views/Import/ImportErrorView.swift @@ -15,7 +15,9 @@ struct ImportErrorView: View { var body: some View { VStack(spacing: 20) { Image(systemName: "exclamationmark.triangle.fill") - .font(.system(size: 48)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemRed)) VStack(spacing: 6) { diff --git a/TablePro/Views/Import/ImportSuccessView.swift b/TablePro/Views/Import/ImportSuccessView.swift index e020f079a..6c80a8714 100644 --- a/TablePro/Views/Import/ImportSuccessView.swift +++ b/TablePro/Views/Import/ImportSuccessView.swift @@ -21,11 +21,15 @@ struct ImportSuccessView: View { VStack(spacing: 20) { if hasErrors { Image(systemName: "exclamationmark.triangle.fill") - .font(.system(size: 48)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemYellow)) } else { Image(systemName: "checkmark.circle.fill") - .font(.system(size: 48)) + .font(.largeTitle) + .imageScale(.large) + .symbolRenderingMode(.hierarchical) .foregroundStyle(Color(nsColor: .systemGreen)) } diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index 8729f98dd..af78d499f 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -744,9 +744,10 @@ struct MainEditorContentView: View { VStack(spacing: 20) { // Icon Image(systemName: "tablecells") - .font(.system(size: 56)) - .foregroundStyle(.quaternary) + .font(.largeTitle) + .imageScale(.large) .symbolRenderingMode(.hierarchical) + .foregroundStyle(.quaternary) // Title Text("No tabs open") diff --git a/TablePro/Views/Main/Child/MainStatusBarView.swift b/TablePro/Views/Main/Child/MainStatusBarView.swift index 61bfb1958..34b816cd0 100644 --- a/TablePro/Views/Main/Child/MainStatusBarView.swift +++ b/TablePro/Views/Main/Child/MainStatusBarView.swift @@ -117,8 +117,7 @@ struct MainStatusBarView: View { Text("Fetch All") .font(.caption) } - .buttonStyle(.plain) - .foregroundStyle(.tint) + .buttonStyle(.link) } if let statusMessage = snapshot.statusMessage { diff --git a/TablePro/Views/RightSidebar/EditableFieldView.swift b/TablePro/Views/RightSidebar/EditableFieldView.swift index ce2f0ae01..010558bbf 100644 --- a/TablePro/Views/RightSidebar/EditableFieldView.swift +++ b/TablePro/Views/RightSidebar/EditableFieldView.swift @@ -111,7 +111,7 @@ internal struct FieldDetailView: View { Text("truncated") .font(.caption2.weight(.medium)) .foregroundStyle(Color(nsColor: .systemOrange)) - .padding(.horizontal, 5) + .padding(.horizontal, 6) .padding(.vertical, 1) .background(Color(nsColor: .systemOrange).opacity(0.15)) .clipShape(Capsule()) diff --git a/TablePro/Views/Sidebar/FavoriteRowView.swift b/TablePro/Views/Sidebar/FavoriteRowView.swift index efee08ead..7202d290f 100644 --- a/TablePro/Views/Sidebar/FavoriteRowView.swift +++ b/TablePro/Views/Sidebar/FavoriteRowView.swift @@ -33,7 +33,7 @@ internal struct FavoriteRowView: View { Text(keyword) .font(.system(.caption, design: .monospaced).weight(.medium)) .foregroundStyle(.secondary) - .padding(.horizontal, 5) + .padding(.horizontal, 6) .padding(.vertical, 1) .background( Capsule() diff --git a/TablePro/Views/Sidebar/RedisKeyTreeView.swift b/TablePro/Views/Sidebar/RedisKeyTreeView.swift index 4f86a10a0..af9e9dd1f 100644 --- a/TablePro/Views/Sidebar/RedisKeyTreeView.swift +++ b/TablePro/Views/Sidebar/RedisKeyTreeView.swift @@ -76,7 +76,7 @@ internal struct RedisKeyTreeView: View { Text("\(keyCount)") .font(.caption2) .foregroundStyle(.secondary) - .padding(.horizontal, 5) + .padding(.horizontal, 6) .padding(.vertical, 1) .background(.quaternary, in: Capsule()) } diff --git a/TablePro/Views/Toolbar/ExecutionIndicatorView.swift b/TablePro/Views/Toolbar/ExecutionIndicatorView.swift index 9e5c95e64..7e6a6b3ad 100644 --- a/TablePro/Views/Toolbar/ExecutionIndicatorView.swift +++ b/TablePro/Views/Toolbar/ExecutionIndicatorView.swift @@ -17,7 +17,7 @@ struct ExecutionIndicatorView: View { var onCancel: (() -> Void)? var body: some View { - HStack(spacing: 5) { + HStack(spacing: 4) { if isExecuting { ProgressView() .controlSize(.small)