diff --git a/CHANGELOG.md b/CHANGELOG.md index 483d23d4c..b6cf82e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Structure tab: modify existing tables (add, modify, drop columns, indexes, foreign keys, primary keys) +### Changed + +- Theme system: UI colors now use native macOS semantic colors (labelColor, separatorColor, etc.) by default, adapting automatically to light/dark mode, accent color, and high contrast settings. Custom themes can still override with hex values. +- Theme system: removed Layout tab (typography, spacing, corner radius customization). Views now use native Apple text styles (.body, .caption, .title3, etc.) for accessibility and Dynamic Type support. +- Empty state views now use native ContentUnavailableView + +### Fixed + +- About window no longer retains memory after closing + ## [0.33.0] - 2026-04-19 ### Added diff --git a/TablePro/AppDelegate.swift b/TablePro/AppDelegate.swift index f4c322298..c40073fe8 100644 --- a/TablePro/AppDelegate.swift +++ b/TablePro/AppDelegate.swift @@ -206,7 +206,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationWillTerminate(_ notification: Notification) { LinkedFolderWatcher.shared.stop() - UserDefaults.standard.synchronize() SSHTunnelManager.shared.terminateAllProcessesSync() } diff --git a/TablePro/Core/Vim/VimEngine.swift b/TablePro/Core/Vim/VimEngine.swift index ef67fe2a1..c07e361c7 100644 --- a/TablePro/Core/Vim/VimEngine.swift +++ b/TablePro/Core/Vim/VimEngine.swift @@ -379,6 +379,7 @@ final class VimEngine { let range = NSRange(location: pos, length: deleteCount) register.text = buffer.string(in: range) register.isLinewise = false + register.syncToPasteboard() buffer.replaceCharacters(in: range, with: "") } return true @@ -489,6 +490,7 @@ final class VimEngine { if sel.length > 0 { register.text = buffer.string(in: sel) register.isLinewise = isLinewise + register.syncToPasteboard() buffer.replaceCharacters(in: sel, with: "") } mode = .normal @@ -499,6 +501,7 @@ final class VimEngine { if sel.length > 0 { register.text = buffer.string(in: sel) register.isLinewise = isLinewise + register.syncToPasteboard() } mode = .normal buffer.setSelectedRange(NSRange(location: sel.location, length: 0)) @@ -509,6 +512,7 @@ final class VimEngine { if sel.length > 0 { register.text = buffer.string(in: sel) register.isLinewise = isLinewise + register.syncToPasteboard() buffer.replaceCharacters(in: sel, with: "") } mode = .insert @@ -750,6 +754,7 @@ final class VimEngine { let deleteRange = NSRange(location: startRange.location, length: endOffset - startRange.location) register.text = buffer.string(in: deleteRange) register.isLinewise = true + register.syncToPasteboard() buffer.replaceCharacters(in: deleteRange, with: "") // Position cursor at start of next line (or current position if at end) let newPos = min(startRange.location, max(0, buffer.length - 1)) @@ -773,6 +778,7 @@ final class VimEngine { let yankRange = NSRange(location: startRange.location, length: endOffset - startRange.location) register.text = buffer.string(in: yankRange) register.isLinewise = true + register.syncToPasteboard() } private func changeLine(_ count: Int, in buffer: VimTextBuffer) { @@ -791,6 +797,7 @@ final class VimEngine { let deleteRange = NSRange(location: startRange.location, length: deleteEnd - startRange.location) register.text = buffer.string(in: deleteRange) register.isLinewise = true + register.syncToPasteboard() buffer.replaceCharacters(in: deleteRange, with: "") buffer.setSelectedRange(NSRange(location: startRange.location, length: 0)) mode = .insert @@ -867,6 +874,7 @@ final class VimEngine { register.text = buffer.string(in: range) register.isLinewise = linewise + register.syncToPasteboard() switch op { case .delete: diff --git a/TablePro/Core/Vim/VimRegister.swift b/TablePro/Core/Vim/VimRegister.swift index 4a084dfd0..2483e922a 100644 --- a/TablePro/Core/Vim/VimRegister.swift +++ b/TablePro/Core/Vim/VimRegister.swift @@ -10,15 +10,15 @@ import AppKit /// Vim register for yank/delete/paste operations struct VimRegister { /// The stored text content - var text: String = "" { - didSet { - if !text.isEmpty { - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(text, forType: .string) - } - } - } + var text: String = "" /// Whether the text was yanked/deleted linewise (entire lines) var isLinewise: Bool = false + + /// Sync the register content to the system pasteboard + func syncToPasteboard() { + guard !text.isEmpty else { return } + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(text, forType: .string) + } } diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index 0723d11c8..c13b4a31e 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -4619,6 +4619,7 @@ } }, "Animations" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -5897,6 +5898,7 @@ } }, "Body" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -6325,6 +6327,7 @@ } }, "Caption" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -9372,6 +9375,7 @@ } }, "Corner Radius" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -12595,6 +12599,7 @@ } }, "Duplicate it to customize colors and layout." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -12615,6 +12620,9 @@ } } } + }, + "Duplicate it to customize colors." : { + }, "Duplicate Row" : { "localizations" : { @@ -13964,6 +13972,9 @@ } } } + }, + "Execute All Statements" : { + }, "Execute all statements in a single transaction. If any statement fails, all changes are rolled back." : { "extractionState" : "stale", @@ -15023,6 +15034,7 @@ } }, "Extra Large" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -15887,6 +15899,7 @@ } }, "Fast" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -17587,6 +17600,7 @@ } }, "Huge" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -17763,6 +17777,7 @@ } }, "Icon Sizes" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -19940,6 +19955,7 @@ } }, "Large" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -20139,6 +20155,7 @@ } }, "Layout" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -21058,6 +21075,7 @@ } }, "Massive" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -21326,6 +21344,7 @@ } }, "Medium" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -29324,6 +29343,9 @@ } } } + }, + "Reset to System Default" : { + }, "Reset Zoom" : { "localizations" : { @@ -29770,6 +29792,7 @@ } }, "Row Heights" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -32605,6 +32628,7 @@ } }, "Slow" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -32630,6 +32654,7 @@ }, "Small" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -32696,6 +32721,7 @@ } }, "Smooth" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -32899,6 +32925,7 @@ } }, "Spacing" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -33855,6 +33882,7 @@ } }, "Status Dot" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -36354,6 +36382,7 @@ } }, "Tiny" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -36376,6 +36405,7 @@ } }, "Tiny Dot" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -36398,6 +36428,7 @@ } }, "Title 2" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -36420,6 +36451,7 @@ } }, "Title 3" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -37388,6 +37420,7 @@ } }, "Typography" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { diff --git a/TablePro/Resources/Themes/tablepro.default-dark.json b/TablePro/Resources/Themes/tablepro.default-dark.json index 0767b0fa9..a8fae8994 100644 --- a/TablePro/Resources/Themes/tablepro.default-dark.json +++ b/TablePro/Resources/Themes/tablepro.default-dark.json @@ -38,16 +38,7 @@ "focusBorder": "#007AFF" }, "ui": { - "windowBackground": "#1E1E1E", - "controlBackground": "#2D2D2D", - "cardBackground": "#252526", - "border": "#3C3C3C", - "primaryText": "#D4D4D4", - "secondaryText": "#A0A0A0", - "tertiaryText": "#6D6D6D", "accentColor": null, - "selectionBackground": "#264F78", - "hoverBackground": "#007AFF0D", "status": { "success": "#32D74B", "warning": "#FF9F0A", @@ -60,72 +51,10 @@ "autoIncrement": "#BF5AF226" } }, - "sidebar": { - "background": "#252526", - "text": "#CCCCCC", - "selectedItem": "#094771", - "hover": "#2A2D2E", - "sectionHeader": "#858585" - }, - "toolbar": { - "secondaryText": "#A0A0A0", - "tertiaryText": "#6D6D6D" - }, "fonts": { "editorFontFamily": "System Mono", "editorFontSize": 13, "dataGridFontFamily": "System Mono", "dataGridFontSize": 13 - }, - "spacing": { - "xxxs": 2, - "xxs": 4, - "xs": 8, - "sm": 12, - "md": 16, - "lg": 20, - "xl": 24, - "listRowInsets": { - "top": 4, - "leading": 8, - "bottom": 4, - "trailing": 8 - } - }, - "typography": { - "tiny": 9, - "caption": 10, - "small": 11, - "medium": 12, - "body": 13, - "title3": 15, - "title2": 17 - }, - "iconSizes": { - "tinyDot": 6, - "statusDot": 8, - "small": 12, - "default": 14, - "medium": 16, - "large": 20, - "extraLarge": 24, - "huge": 32, - "massive": 64 - }, - "cornerRadius": { - "small": 4, - "medium": 6, - "large": 8 - }, - "rowHeights": { - "compact": 24, - "table": 32, - "comfortable": 44 - }, - "animations": { - "fast": 0.1, - "normal": 0.15, - "smooth": 0.2, - "slow": 0.3 } } diff --git a/TablePro/Resources/Themes/tablepro.default-light.json b/TablePro/Resources/Themes/tablepro.default-light.json index f5e839b3a..bf002ab64 100644 --- a/TablePro/Resources/Themes/tablepro.default-light.json +++ b/TablePro/Resources/Themes/tablepro.default-light.json @@ -38,16 +38,7 @@ "focusBorder": "#007AFF" }, "ui": { - "windowBackground": "#ECECEC", - "controlBackground": "#FFFFFF", - "cardBackground": "#F5F5F5", - "border": "#D5D5D5", - "primaryText": "#000000", - "secondaryText": "#8E8E93", - "tertiaryText": "#C7C7CC", "accentColor": null, - "selectionBackground": "#B4D8FD", - "hoverBackground": "#007AFF0D", "status": { "success": "#248A3D", "warning": "#C55B00", @@ -60,72 +51,10 @@ "autoIncrement": "#AF52DE26" } }, - "sidebar": { - "background": "#F5F5F5", - "text": "#000000", - "selectedItem": "#007AFF33", - "hover": "#0000000A", - "sectionHeader": "#8E8E93" - }, - "toolbar": { - "secondaryText": "#8E8E93", - "tertiaryText": "#C7C7CC" - }, "fonts": { "editorFontFamily": "System Mono", "editorFontSize": 13, "dataGridFontFamily": "System Mono", "dataGridFontSize": 13 - }, - "spacing": { - "xxxs": 2, - "xxs": 4, - "xs": 8, - "sm": 12, - "md": 16, - "lg": 20, - "xl": 24, - "listRowInsets": { - "top": 4, - "leading": 8, - "bottom": 4, - "trailing": 8 - } - }, - "typography": { - "tiny": 9, - "caption": 10, - "small": 11, - "medium": 12, - "body": 13, - "title3": 15, - "title2": 17 - }, - "iconSizes": { - "tinyDot": 6, - "statusDot": 8, - "small": 12, - "default": 14, - "medium": 16, - "large": 20, - "extraLarge": 24, - "huge": 32, - "massive": 64 - }, - "cornerRadius": { - "small": 4, - "medium": 6, - "large": 8 - }, - "rowHeights": { - "compact": 24, - "table": 32, - "comfortable": 44 - }, - "animations": { - "fast": 0.1, - "normal": 0.15, - "smooth": 0.2, - "slow": 0.3 } } diff --git a/TablePro/Resources/Themes/tablepro.dracula.json b/TablePro/Resources/Themes/tablepro.dracula.json index 32d673d8b..128d186d1 100644 --- a/TablePro/Resources/Themes/tablepro.dracula.json +++ b/TablePro/Resources/Themes/tablepro.dracula.json @@ -76,56 +76,5 @@ "editorFontSize": 13, "dataGridFontFamily": "System Mono", "dataGridFontSize": 13 - }, - "spacing": { - "xxxs": 2, - "xxs": 4, - "xs": 8, - "sm": 12, - "md": 16, - "lg": 20, - "xl": 24, - "listRowInsets": { - "top": 4, - "leading": 8, - "bottom": 4, - "trailing": 8 - } - }, - "typography": { - "tiny": 9, - "caption": 10, - "small": 11, - "medium": 12, - "body": 13, - "title3": 15, - "title2": 17 - }, - "iconSizes": { - "tinyDot": 6, - "statusDot": 8, - "small": 12, - "default": 14, - "medium": 16, - "large": 20, - "extraLarge": 24, - "huge": 32, - "massive": 64 - }, - "cornerRadius": { - "small": 4, - "medium": 6, - "large": 8 - }, - "rowHeights": { - "compact": 24, - "table": 32, - "comfortable": 44 - }, - "animations": { - "fast": 0.1, - "normal": 0.15, - "smooth": 0.2, - "slow": 0.3 } } diff --git a/TablePro/Resources/Themes/tablepro.nord.json b/TablePro/Resources/Themes/tablepro.nord.json index 66bca5a44..dc1d41f93 100644 --- a/TablePro/Resources/Themes/tablepro.nord.json +++ b/TablePro/Resources/Themes/tablepro.nord.json @@ -76,56 +76,5 @@ "editorFontSize": 13, "dataGridFontFamily": "System Mono", "dataGridFontSize": 13 - }, - "spacing": { - "xxxs": 2, - "xxs": 4, - "xs": 8, - "sm": 12, - "md": 16, - "lg": 20, - "xl": 24, - "listRowInsets": { - "top": 4, - "leading": 8, - "bottom": 4, - "trailing": 8 - } - }, - "typography": { - "tiny": 9, - "caption": 10, - "small": 11, - "medium": 12, - "body": 13, - "title3": 15, - "title2": 17 - }, - "iconSizes": { - "tinyDot": 6, - "statusDot": 8, - "small": 12, - "default": 14, - "medium": 16, - "large": 20, - "extraLarge": 24, - "huge": 32, - "massive": 64 - }, - "cornerRadius": { - "small": 4, - "medium": 6, - "large": 8 - }, - "rowHeights": { - "compact": 24, - "table": 32, - "comfortable": 44 - }, - "animations": { - "fast": 0.1, - "normal": 0.15, - "smooth": 0.2, - "slow": 0.3 } } diff --git a/TablePro/Theme/ResolvedThemeColors.swift b/TablePro/Theme/ResolvedThemeColors.swift index e99da9311..76bbd85b7 100644 --- a/TablePro/Theme/ResolvedThemeColors.swift +++ b/TablePro/Theme/ResolvedThemeColors.swift @@ -179,21 +179,22 @@ struct ResolvedUIColors { let badgeAutoIncrementSwiftUI: Color init(from colors: UIThemeColors) { - windowBackground = colors.windowBackground.nsColor - windowBackgroundSwiftUI = colors.windowBackground.swiftUIColor - controlBackground = colors.controlBackground.nsColor - controlBackgroundSwiftUI = colors.controlBackground.swiftUIColor - cardBackground = colors.cardBackground.nsColor - cardBackgroundSwiftUI = colors.cardBackground.swiftUIColor - border = colors.border.nsColor - borderSwiftUI = colors.border.swiftUIColor + windowBackground = colors.windowBackground?.nsColor ?? .windowBackgroundColor + windowBackgroundSwiftUI = colors.windowBackground?.swiftUIColor ?? Color(nsColor: .windowBackgroundColor) + controlBackground = colors.controlBackground?.nsColor ?? .controlBackgroundColor + controlBackgroundSwiftUI = colors.controlBackground?.swiftUIColor + ?? Color(nsColor: .controlBackgroundColor) + cardBackground = colors.cardBackground?.nsColor ?? .controlBackgroundColor + cardBackgroundSwiftUI = colors.cardBackground?.swiftUIColor ?? Color(nsColor: .controlBackgroundColor) + border = colors.border?.nsColor ?? .separatorColor + borderSwiftUI = colors.border?.swiftUIColor ?? Color(nsColor: .separatorColor) - primaryText = colors.primaryText.nsColor - primaryTextSwiftUI = colors.primaryText.swiftUIColor - secondaryText = colors.secondaryText.nsColor - secondaryTextSwiftUI = colors.secondaryText.swiftUIColor - tertiaryText = colors.tertiaryText.nsColor - tertiaryTextSwiftUI = colors.tertiaryText.swiftUIColor + primaryText = colors.primaryText?.nsColor ?? .labelColor + primaryTextSwiftUI = colors.primaryText?.swiftUIColor ?? Color(nsColor: .labelColor) + secondaryText = colors.secondaryText?.nsColor ?? .secondaryLabelColor + secondaryTextSwiftUI = colors.secondaryText?.swiftUIColor ?? Color(nsColor: .secondaryLabelColor) + tertiaryText = colors.tertiaryText?.nsColor ?? .tertiaryLabelColor + tertiaryTextSwiftUI = colors.tertiaryText?.swiftUIColor ?? Color(nsColor: .tertiaryLabelColor) if let accent = colors.accentColor { accentColor = accent.nsColor @@ -203,10 +204,12 @@ struct ResolvedUIColors { accentColorSwiftUI = nil } - selectionBackground = colors.selectionBackground.nsColor - selectionBackgroundSwiftUI = colors.selectionBackground.swiftUIColor - hoverBackground = colors.hoverBackground.nsColor - hoverBackgroundSwiftUI = colors.hoverBackground.swiftUIColor + selectionBackground = colors.selectionBackground?.nsColor ?? .selectedContentBackgroundColor + selectionBackgroundSwiftUI = colors.selectionBackground?.swiftUIColor + ?? Color(nsColor: .selectedContentBackgroundColor) + hoverBackground = colors.hoverBackground?.nsColor ?? .unemphasizedSelectedContentBackgroundColor + hoverBackgroundSwiftUI = colors.hoverBackground?.swiftUIColor + ?? Color(nsColor: .unemphasizedSelectedContentBackgroundColor) success = colors.status.success.nsColor successSwiftUI = colors.status.success.swiftUIColor @@ -239,16 +242,18 @@ struct ResolvedSidebarColors { let sectionHeaderSwiftUI: Color init(from colors: SidebarThemeColors) { - background = colors.background.nsColor - backgroundSwiftUI = colors.background.swiftUIColor - text = colors.text.nsColor - textSwiftUI = colors.text.swiftUIColor - selectedItem = colors.selectedItem.nsColor - selectedItemSwiftUI = colors.selectedItem.swiftUIColor - hover = colors.hover.nsColor - hoverSwiftUI = colors.hover.swiftUIColor - sectionHeader = colors.sectionHeader.nsColor - sectionHeaderSwiftUI = colors.sectionHeader.swiftUIColor + background = colors.background?.nsColor ?? .windowBackgroundColor + backgroundSwiftUI = colors.background?.swiftUIColor ?? Color(nsColor: .windowBackgroundColor) + text = colors.text?.nsColor ?? .labelColor + textSwiftUI = colors.text?.swiftUIColor ?? Color(nsColor: .labelColor) + selectedItem = colors.selectedItem?.nsColor ?? .selectedContentBackgroundColor + selectedItemSwiftUI = colors.selectedItem?.swiftUIColor + ?? Color(nsColor: .selectedContentBackgroundColor) + hover = colors.hover?.nsColor ?? .unemphasizedSelectedContentBackgroundColor + hoverSwiftUI = colors.hover?.swiftUIColor + ?? Color(nsColor: .unemphasizedSelectedContentBackgroundColor) + sectionHeader = colors.sectionHeader?.nsColor ?? .secondaryLabelColor + sectionHeaderSwiftUI = colors.sectionHeader?.swiftUIColor ?? Color(nsColor: .secondaryLabelColor) } } @@ -259,10 +264,10 @@ struct ResolvedToolbarColors { let tertiaryTextSwiftUI: Color init(from colors: ToolbarThemeColors) { - secondaryText = colors.secondaryText.nsColor - secondaryTextSwiftUI = colors.secondaryText.swiftUIColor - tertiaryText = colors.tertiaryText.nsColor - tertiaryTextSwiftUI = colors.tertiaryText.swiftUIColor + secondaryText = colors.secondaryText?.nsColor ?? .secondaryLabelColor + secondaryTextSwiftUI = colors.secondaryText?.swiftUIColor ?? Color(nsColor: .secondaryLabelColor) + tertiaryText = colors.tertiaryText?.nsColor ?? .tertiaryLabelColor + tertiaryTextSwiftUI = colors.tertiaryText?.swiftUIColor ?? Color(nsColor: .tertiaryLabelColor) } } diff --git a/TablePro/Theme/ThemeColors.swift b/TablePro/Theme/ThemeColors.swift index 1c0577c78..02148f41a 100644 --- a/TablePro/Theme/ThemeColors.swift +++ b/TablePro/Theme/ThemeColors.swift @@ -272,45 +272,45 @@ internal struct BadgeColors: Codable, Equatable, Sendable { // MARK: - UI Theme Colors internal struct UIThemeColors: Codable, Equatable, Sendable { - var windowBackground: String - var controlBackground: String - var cardBackground: String - var border: String - var primaryText: String - var secondaryText: String - var tertiaryText: String + var windowBackground: String? + var controlBackground: String? + var cardBackground: String? + var border: String? + var primaryText: String? + var secondaryText: String? + var tertiaryText: String? var accentColor: String? - var selectionBackground: String - var hoverBackground: String + var selectionBackground: String? + var hoverBackground: String? var status: StatusColors var badges: BadgeColors static let defaultLight = UIThemeColors( - windowBackground: "#ECECEC", - controlBackground: "#FFFFFF", - cardBackground: "#FFFFFF", - border: "#D1D1D6", - primaryText: "#000000", - secondaryText: "#3C3C43", - tertiaryText: "#8E8E93", + windowBackground: nil, + controlBackground: nil, + cardBackground: nil, + border: nil, + primaryText: nil, + secondaryText: nil, + tertiaryText: nil, accentColor: nil, - selectionBackground: "#0A84FF", - hoverBackground: "#F2F2F7", + selectionBackground: nil, + hoverBackground: nil, status: .defaultLight, badges: .defaultLight ) init( - windowBackground: String, - controlBackground: String, - cardBackground: String, - border: String, - primaryText: String, - secondaryText: String, - tertiaryText: String, + windowBackground: String?, + controlBackground: String?, + cardBackground: String?, + border: String?, + primaryText: String?, + secondaryText: String?, + tertiaryText: String?, accentColor: String?, - selectionBackground: String, - hoverBackground: String, + selectionBackground: String?, + hoverBackground: String?, status: StatusColors, badges: BadgeColors ) { @@ -333,19 +333,15 @@ internal struct UIThemeColors: Codable, Equatable, Sendable { let fallback = UIThemeColors.defaultLight windowBackground = try container.decodeIfPresent(String.self, forKey: .windowBackground) - ?? fallback.windowBackground controlBackground = try container.decodeIfPresent(String.self, forKey: .controlBackground) - ?? fallback.controlBackground - cardBackground = try container.decodeIfPresent(String.self, forKey: .cardBackground) ?? fallback.cardBackground - border = try container.decodeIfPresent(String.self, forKey: .border) ?? fallback.border - primaryText = try container.decodeIfPresent(String.self, forKey: .primaryText) ?? fallback.primaryText - secondaryText = try container.decodeIfPresent(String.self, forKey: .secondaryText) ?? fallback.secondaryText - tertiaryText = try container.decodeIfPresent(String.self, forKey: .tertiaryText) ?? fallback.tertiaryText + cardBackground = try container.decodeIfPresent(String.self, forKey: .cardBackground) + border = try container.decodeIfPresent(String.self, forKey: .border) + primaryText = try container.decodeIfPresent(String.self, forKey: .primaryText) + secondaryText = try container.decodeIfPresent(String.self, forKey: .secondaryText) + tertiaryText = try container.decodeIfPresent(String.self, forKey: .tertiaryText) accentColor = try container.decodeIfPresent(String.self, forKey: .accentColor) selectionBackground = try container.decodeIfPresent(String.self, forKey: .selectionBackground) - ?? fallback.selectionBackground hoverBackground = try container.decodeIfPresent(String.self, forKey: .hoverBackground) - ?? fallback.hoverBackground status = try container.decodeIfPresent(StatusColors.self, forKey: .status) ?? fallback.status badges = try container.decodeIfPresent(BadgeColors.self, forKey: .badges) ?? fallback.badges } @@ -354,21 +350,21 @@ internal struct UIThemeColors: Codable, Equatable, Sendable { // MARK: - Sidebar Theme Colors internal struct SidebarThemeColors: Codable, Equatable, Sendable { - var background: String - var text: String - var selectedItem: String - var hover: String - var sectionHeader: String + var background: String? + var text: String? + var selectedItem: String? + var hover: String? + var sectionHeader: String? static let defaultLight = SidebarThemeColors( - background: "#F5F5F5", - text: "#000000", - selectedItem: "#0A84FF", - hover: "#E5E5EA", - sectionHeader: "#8E8E93" + background: nil, + text: nil, + selectedItem: nil, + hover: nil, + sectionHeader: nil ) - init(background: String, text: String, selectedItem: String, hover: String, sectionHeader: String) { + init(background: String?, text: String?, selectedItem: String?, hover: String?, sectionHeader: String?) { self.background = background self.text = text self.selectedItem = selectedItem @@ -378,37 +374,35 @@ internal struct SidebarThemeColors: Codable, Equatable, Sendable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = SidebarThemeColors.defaultLight - background = try container.decodeIfPresent(String.self, forKey: .background) ?? fallback.background - text = try container.decodeIfPresent(String.self, forKey: .text) ?? fallback.text - selectedItem = try container.decodeIfPresent(String.self, forKey: .selectedItem) ?? fallback.selectedItem - hover = try container.decodeIfPresent(String.self, forKey: .hover) ?? fallback.hover - sectionHeader = try container.decodeIfPresent(String.self, forKey: .sectionHeader) ?? fallback.sectionHeader + background = try container.decodeIfPresent(String.self, forKey: .background) + text = try container.decodeIfPresent(String.self, forKey: .text) + selectedItem = try container.decodeIfPresent(String.self, forKey: .selectedItem) + hover = try container.decodeIfPresent(String.self, forKey: .hover) + sectionHeader = try container.decodeIfPresent(String.self, forKey: .sectionHeader) } } // MARK: - Toolbar Theme Colors internal struct ToolbarThemeColors: Codable, Equatable, Sendable { - var secondaryText: String - var tertiaryText: String + var secondaryText: String? + var tertiaryText: String? static let defaultLight = ToolbarThemeColors( - secondaryText: "#3C3C43", - tertiaryText: "#8E8E93" + secondaryText: nil, + tertiaryText: nil ) - init(secondaryText: String, tertiaryText: String) { + init(secondaryText: String?, tertiaryText: String?) { self.secondaryText = secondaryText self.tertiaryText = tertiaryText } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ToolbarThemeColors.defaultLight - secondaryText = try container.decodeIfPresent(String.self, forKey: .secondaryText) ?? fallback.secondaryText - tertiaryText = try container.decodeIfPresent(String.self, forKey: .tertiaryText) ?? fallback.tertiaryText + secondaryText = try container.decodeIfPresent(String.self, forKey: .secondaryText) + tertiaryText = try container.decodeIfPresent(String.self, forKey: .tertiaryText) } } diff --git a/TablePro/Theme/ThemeDefinition.swift b/TablePro/Theme/ThemeDefinition.swift index 39b4a8f72..f0eaddf40 100644 --- a/TablePro/Theme/ThemeDefinition.swift +++ b/TablePro/Theme/ThemeDefinition.swift @@ -12,12 +12,6 @@ internal struct ThemeDefinition: Codable, Identifiable, Equatable, Sendable { var sidebar: SidebarThemeColors var toolbar: ToolbarThemeColors var fonts: ThemeFonts - var spacing: ThemeSpacing - var typography: ThemeTypography - var iconSizes: ThemeIconSizes - var cornerRadius: ThemeCornerRadius - var rowHeights: ThemeRowHeights - var animations: ThemeAnimations var isBuiltIn: Bool { id.hasPrefix("tablepro.") } var isRegistry: Bool { id.hasPrefix("registry.") } @@ -34,13 +28,7 @@ internal struct ThemeDefinition: Codable, Identifiable, Equatable, Sendable { ui: .defaultLight, sidebar: .defaultLight, toolbar: .defaultLight, - fonts: .default, - spacing: .default, - typography: .default, - iconSizes: .default, - cornerRadius: .default, - rowHeights: .default, - animations: .default + fonts: .default ) init( @@ -54,13 +42,7 @@ internal struct ThemeDefinition: Codable, Identifiable, Equatable, Sendable { ui: UIThemeColors, sidebar: SidebarThemeColors, toolbar: ToolbarThemeColors, - fonts: ThemeFonts, - spacing: ThemeSpacing = .default, - typography: ThemeTypography = .default, - iconSizes: ThemeIconSizes = .default, - cornerRadius: ThemeCornerRadius = .default, - rowHeights: ThemeRowHeights = .default, - animations: ThemeAnimations = .default + fonts: ThemeFonts ) { self.id = id self.name = name @@ -73,12 +55,6 @@ internal struct ThemeDefinition: Codable, Identifiable, Equatable, Sendable { self.sidebar = sidebar self.toolbar = toolbar self.fonts = fonts - self.spacing = spacing - self.typography = typography - self.iconSizes = iconSizes - self.cornerRadius = cornerRadius - self.rowHeights = rowHeights - self.animations = animations } init(from decoder: Decoder) throws { @@ -96,12 +72,6 @@ internal struct ThemeDefinition: Codable, Identifiable, Equatable, Sendable { sidebar = try container.decodeIfPresent(SidebarThemeColors.self, forKey: .sidebar) ?? fallback.sidebar toolbar = try container.decodeIfPresent(ToolbarThemeColors.self, forKey: .toolbar) ?? fallback.toolbar fonts = try container.decodeIfPresent(ThemeFonts.self, forKey: .fonts) ?? fallback.fonts - spacing = try container.decodeIfPresent(ThemeSpacing.self, forKey: .spacing) ?? fallback.spacing - typography = try container.decodeIfPresent(ThemeTypography.self, forKey: .typography) ?? fallback.typography - iconSizes = try container.decodeIfPresent(ThemeIconSizes.self, forKey: .iconSizes) ?? fallback.iconSizes - cornerRadius = try container.decodeIfPresent(ThemeCornerRadius.self, forKey: .cornerRadius) ?? fallback.cornerRadius - rowHeights = try container.decodeIfPresent(ThemeRowHeights.self, forKey: .rowHeights) ?? fallback.rowHeights - animations = try container.decodeIfPresent(ThemeAnimations.self, forKey: .animations) ?? fallback.animations } } diff --git a/TablePro/Theme/ThemeEngine.swift b/TablePro/Theme/ThemeEngine.swift index 41e545cce..84cf3a3e3 100644 --- a/TablePro/Theme/ThemeEngine.swift +++ b/TablePro/Theme/ThemeEngine.swift @@ -430,8 +430,8 @@ extension DatabaseType { extension View { func cardStyle() -> some View { self - .background(ThemeEngine.shared.colors.ui.controlBackgroundSwiftUI) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .background(Color(nsColor: .controlBackgroundColor)) + .clipShape(RoundedRectangle(cornerRadius: 6)) } func toolbarButtonStyle() -> some View { diff --git a/TablePro/Theme/ThemeLayout.swift b/TablePro/Theme/ThemeLayout.swift index 69d44e666..4f796657c 100644 --- a/TablePro/Theme/ThemeLayout.swift +++ b/TablePro/Theme/ThemeLayout.swift @@ -4,7 +4,7 @@ // import Foundation -import SwiftUI + // MARK: - Theme Fonts internal struct ThemeFonts: Codable, Equatable, Sendable { @@ -40,213 +40,3 @@ internal struct ThemeFonts: Codable, Equatable, Sendable { ?? fallback.dataGridFontSize } } - -// MARK: - Theme Spacing - -internal struct ThemeSpacing: Codable, Equatable, Sendable { - var xxxs: CGFloat - var xxs: CGFloat - var xs: CGFloat - var sm: CGFloat - var md: CGFloat - var lg: CGFloat - var xl: CGFloat - var listRowInsets: ThemeEdgeInsets - - static let `default` = ThemeSpacing( - xxxs: 2, xxs: 4, xs: 8, sm: 12, md: 16, lg: 20, xl: 24, - listRowInsets: .default - ) - - init( - xxxs: CGFloat, xxs: CGFloat, xs: CGFloat, sm: CGFloat, - md: CGFloat, lg: CGFloat, xl: CGFloat, listRowInsets: ThemeEdgeInsets - ) { - self.xxxs = xxxs; self.xxs = xxs; self.xs = xs; self.sm = sm - self.md = md; self.lg = lg; self.xl = xl; self.listRowInsets = listRowInsets - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeSpacing.default - xxxs = try container.decodeIfPresent(CGFloat.self, forKey: .xxxs) ?? fallback.xxxs - xxs = try container.decodeIfPresent(CGFloat.self, forKey: .xxs) ?? fallback.xxs - xs = try container.decodeIfPresent(CGFloat.self, forKey: .xs) ?? fallback.xs - sm = try container.decodeIfPresent(CGFloat.self, forKey: .sm) ?? fallback.sm - md = try container.decodeIfPresent(CGFloat.self, forKey: .md) ?? fallback.md - lg = try container.decodeIfPresent(CGFloat.self, forKey: .lg) ?? fallback.lg - xl = try container.decodeIfPresent(CGFloat.self, forKey: .xl) ?? fallback.xl - listRowInsets = try container.decodeIfPresent(ThemeEdgeInsets.self, forKey: .listRowInsets) ?? fallback.listRowInsets - } -} - -internal struct ThemeEdgeInsets: Codable, Equatable, Sendable { - var top: CGFloat - var leading: CGFloat - var bottom: CGFloat - var trailing: CGFloat - - static let `default` = ThemeEdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8) - - var swiftUI: EdgeInsets { EdgeInsets(top: top, leading: leading, bottom: bottom, trailing: trailing) } - var appKit: NSEdgeInsets { NSEdgeInsets(top: top, left: leading, bottom: bottom, right: trailing) } - - init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { - self.top = top; self.leading = leading; self.bottom = bottom; self.trailing = trailing - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeEdgeInsets.default - top = try container.decodeIfPresent(CGFloat.self, forKey: .top) ?? fallback.top - leading = try container.decodeIfPresent(CGFloat.self, forKey: .leading) ?? fallback.leading - bottom = try container.decodeIfPresent(CGFloat.self, forKey: .bottom) ?? fallback.bottom - trailing = try container.decodeIfPresent(CGFloat.self, forKey: .trailing) ?? fallback.trailing - } -} - -// MARK: - Theme Typography - -internal struct ThemeTypography: Codable, Equatable, Sendable { - var tiny: CGFloat - var caption: CGFloat - var small: CGFloat - var medium: CGFloat - var body: CGFloat - var title3: CGFloat - var title2: CGFloat - - static let `default` = ThemeTypography( - tiny: 9, caption: 10, small: 11, medium: 12, body: 13, title3: 15, title2: 17 - ) - - init( - tiny: CGFloat, caption: CGFloat, small: CGFloat, medium: CGFloat, - body: CGFloat, title3: CGFloat, title2: CGFloat - ) { - self.tiny = tiny; self.caption = caption; self.small = small; self.medium = medium - self.body = body; self.title3 = title3; self.title2 = title2 - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeTypography.default - tiny = try container.decodeIfPresent(CGFloat.self, forKey: .tiny) ?? fallback.tiny - caption = try container.decodeIfPresent(CGFloat.self, forKey: .caption) ?? fallback.caption - small = try container.decodeIfPresent(CGFloat.self, forKey: .small) ?? fallback.small - medium = try container.decodeIfPresent(CGFloat.self, forKey: .medium) ?? fallback.medium - body = try container.decodeIfPresent(CGFloat.self, forKey: .body) ?? fallback.body - title3 = try container.decodeIfPresent(CGFloat.self, forKey: .title3) ?? fallback.title3 - title2 = try container.decodeIfPresent(CGFloat.self, forKey: .title2) ?? fallback.title2 - } -} - -// MARK: - Theme Icon Sizes - -internal struct ThemeIconSizes: Codable, Equatable, Sendable { - var tinyDot: CGFloat - var statusDot: CGFloat - var small: CGFloat - var `default`: CGFloat - var medium: CGFloat - var large: CGFloat - var extraLarge: CGFloat - var huge: CGFloat - var massive: CGFloat - - static let `default` = ThemeIconSizes( - tinyDot: 6, statusDot: 8, small: 12, default: 14, medium: 16, - large: 20, extraLarge: 24, huge: 32, massive: 64 - ) - - init( - tinyDot: CGFloat, statusDot: CGFloat, small: CGFloat, `default`: CGFloat, - medium: CGFloat, large: CGFloat, extraLarge: CGFloat, huge: CGFloat, massive: CGFloat - ) { - self.tinyDot = tinyDot; self.statusDot = statusDot; self.small = small - self.`default` = `default`; self.medium = medium; self.large = large - self.extraLarge = extraLarge; self.huge = huge; self.massive = massive - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeIconSizes.default - tinyDot = try container.decodeIfPresent(CGFloat.self, forKey: .tinyDot) ?? fallback.tinyDot - statusDot = try container.decodeIfPresent(CGFloat.self, forKey: .statusDot) ?? fallback.statusDot - small = try container.decodeIfPresent(CGFloat.self, forKey: .small) ?? fallback.small - `default` = try container.decodeIfPresent(CGFloat.self, forKey: .default) ?? fallback.default - medium = try container.decodeIfPresent(CGFloat.self, forKey: .medium) ?? fallback.medium - large = try container.decodeIfPresent(CGFloat.self, forKey: .large) ?? fallback.large - extraLarge = try container.decodeIfPresent(CGFloat.self, forKey: .extraLarge) ?? fallback.extraLarge - huge = try container.decodeIfPresent(CGFloat.self, forKey: .huge) ?? fallback.huge - massive = try container.decodeIfPresent(CGFloat.self, forKey: .massive) ?? fallback.massive - } -} - -// MARK: - Theme Corner Radius - -internal struct ThemeCornerRadius: Codable, Equatable, Sendable { - var small: CGFloat - var medium: CGFloat - var large: CGFloat - - static let `default` = ThemeCornerRadius(small: 4, medium: 6, large: 8) - - init(small: CGFloat, medium: CGFloat, large: CGFloat) { - self.small = small; self.medium = medium; self.large = large - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeCornerRadius.default - small = try container.decodeIfPresent(CGFloat.self, forKey: .small) ?? fallback.small - medium = try container.decodeIfPresent(CGFloat.self, forKey: .medium) ?? fallback.medium - large = try container.decodeIfPresent(CGFloat.self, forKey: .large) ?? fallback.large - } -} - -// MARK: - Theme Row Heights - -internal struct ThemeRowHeights: Codable, Equatable, Sendable { - var compact: CGFloat - var table: CGFloat - var comfortable: CGFloat - - static let `default` = ThemeRowHeights(compact: 24, table: 32, comfortable: 44) - - init(compact: CGFloat, table: CGFloat, comfortable: CGFloat) { - self.compact = compact; self.table = table; self.comfortable = comfortable - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeRowHeights.default - compact = try container.decodeIfPresent(CGFloat.self, forKey: .compact) ?? fallback.compact - table = try container.decodeIfPresent(CGFloat.self, forKey: .table) ?? fallback.table - comfortable = try container.decodeIfPresent(CGFloat.self, forKey: .comfortable) ?? fallback.comfortable - } -} - -// MARK: - Theme Animations - -internal struct ThemeAnimations: Codable, Equatable, Sendable { - var fast: Double - var normal: Double - var smooth: Double - var slow: Double - - static let `default` = ThemeAnimations(fast: 0.1, normal: 0.15, smooth: 0.2, slow: 0.3) - - init(fast: Double, normal: Double, smooth: Double, slow: Double) { - self.fast = fast; self.normal = normal; self.smooth = smooth; self.slow = slow - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let fallback = ThemeAnimations.default - fast = try container.decodeIfPresent(Double.self, forKey: .fast) ?? fallback.fast - normal = try container.decodeIfPresent(Double.self, forKey: .normal) ?? fallback.normal - smooth = try container.decodeIfPresent(Double.self, forKey: .smooth) ?? fallback.smooth - slow = try container.decodeIfPresent(Double.self, forKey: .slow) ?? fallback.slow - } -} diff --git a/TablePro/Views/AIChat/AIChatCodeBlockView.swift b/TablePro/Views/AIChat/AIChatCodeBlockView.swift index 2bf227c16..0c502b08d 100644 --- a/TablePro/Views/AIChat/AIChatCodeBlockView.swift +++ b/TablePro/Views/AIChat/AIChatCodeBlockView.swift @@ -35,7 +35,7 @@ struct AIChatCodeBlockView: View { .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color(nsColor: .separatorColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) } Spacer() @@ -291,9 +291,9 @@ private struct CodeBlockGroupBoxStyle: GroupBoxStyle { configuration.content } .background(Color(nsColor: .controlBackgroundColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.large)) + .clipShape(RoundedRectangle(cornerRadius: 8)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.large) + RoundedRectangle(cornerRadius: 8) .stroke(Color(nsColor: .separatorColor), lineWidth: 1) ) } diff --git a/TablePro/Views/AIChat/AIChatMessageView.swift b/TablePro/Views/AIChat/AIChatMessageView.swift index ec19b44b8..7adcdfeb2 100644 --- a/TablePro/Views/AIChat/AIChatMessageView.swift +++ b/TablePro/Views/AIChat/AIChatMessageView.swift @@ -51,7 +51,7 @@ struct AIChatMessageView: View { } .padding(8) .background(Color.accentColor.opacity(0.06)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.large)) + .clipShape(RoundedRectangle(cornerRadius: 8)) } else { // Assistant: role header above content roleHeader diff --git a/TablePro/Views/About/AboutView.swift b/TablePro/Views/About/AboutView.swift index 792bd8012..1393e9c73 100644 --- a/TablePro/Views/About/AboutView.swift +++ b/TablePro/Views/About/AboutView.swift @@ -12,31 +12,31 @@ struct AboutView: View { @State private var hoveredLink: String? var body: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.md) { + VStack(spacing: 16) { Spacer() Image(nsImage: NSApp.applicationIconImage) .resizable() .frame(width: 80, height: 80) - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.xxs) { + VStack(spacing: 4) { Text("TablePro") .font( .system( - size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge, weight: .semibold, + size: 24, weight: .semibold, design: .rounded)) Text("Version \(Bundle.main.appVersion) (Build \(Bundle.main.buildNumber))") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } Text("© 2026 Ngo Quoc Dat.\n\(String(localized: "All rights reserved."))") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.tertiary) .multilineTextAlignment(.center) - HStack(spacing: ThemeEngine.shared.activeTheme.spacing.lg) { + HStack(spacing: 20) { linkButton( title: String(localized: "Website"), icon: "globe", @@ -70,11 +70,11 @@ struct AboutView: View { NSWorkspace.shared.open(link) } } label: { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.xxxs) { + VStack(spacing: 2) { Image(systemName: icon) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) Text(title) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .underline(hoveredLink == title) } .foregroundStyle(.secondary) diff --git a/TablePro/Views/About/AboutWindowController.swift b/TablePro/Views/About/AboutWindowController.swift index 319b419d2..c07fd2d21 100644 --- a/TablePro/Views/About/AboutWindowController.swift +++ b/TablePro/Views/About/AboutWindowController.swift @@ -40,5 +40,15 @@ final class AboutWindowController { panel.center() panel.makeKeyAndOrderFront(nil) self.panel = panel + + NotificationCenter.default.addObserver( + forName: NSWindow.willCloseNotification, + object: panel, + queue: .main + ) { [weak self] _ in + Task { @MainActor in + self?.panel = nil + } + } } } diff --git a/TablePro/Views/Components/EmptyStateView.swift b/TablePro/Views/Components/EmptyStateView.swift index 1568e23ac..33fc6920e 100644 --- a/TablePro/Views/Components/EmptyStateView.swift +++ b/TablePro/Views/Components/EmptyStateView.swift @@ -30,43 +30,17 @@ struct EmptyStateView: View { } var body: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.sm) { - // Icon - Image(systemName: icon) - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.huge)) - .foregroundStyle(ThemeEngine.shared.colors.ui.tertiaryTextSwiftUI) - .padding(.bottom, ThemeEngine.shared.activeTheme.spacing.xxs) - - // Title - Text(title) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) - .foregroundStyle(ThemeEngine.shared.colors.ui.secondaryTextSwiftUI) - - // Description (optional) - if let description = description { + ContentUnavailableView { + Label(title, systemImage: icon) + } description: { + if let description { Text(description) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) - .foregroundStyle(ThemeEngine.shared.colors.ui.tertiaryTextSwiftUI) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) } - - // Action button (optional) - if let actionTitle = actionTitle, let action = action { - Button(action: action) { - HStack(spacing: 4) { - Image(systemName: "plus") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) - Text(actionTitle) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) - } - } - .buttonStyle(.borderless) - .padding(.top, ThemeEngine.shared.activeTheme.spacing.xxs) + } actions: { + if let actionTitle, let action { + Button(actionTitle, action: action) } } - .frame(maxWidth: .infinity) - .padding(.vertical, 40) } } diff --git a/TablePro/Views/Components/SQLReviewPopover.swift b/TablePro/Views/Components/SQLReviewPopover.swift index a66cded27..4ac48f15c 100644 --- a/TablePro/Views/Components/SQLReviewPopover.swift +++ b/TablePro/Views/Components/SQLReviewPopover.swift @@ -48,7 +48,7 @@ struct SQLReviewPopover: View { private var contentHeight: CGFloat { let lineHeight: CGFloat = 18 let headerHeight: CGFloat = 30 - let padding: CGFloat = ThemeEngine.shared.activeTheme.spacing.md * 2 + ThemeEngine.shared.activeTheme.spacing.sm + let padding: CGFloat = 16 * 2 + 12 let editorInsets: CGFloat = 16 // top + bottom content insets // Count lines directly from statements to avoid recomputing combinedSQL. @@ -72,7 +72,7 @@ struct SQLReviewPopover: View { } var body: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.sm) { + VStack(spacing: 12) { headerView if statements.isEmpty { emptyState @@ -80,7 +80,7 @@ struct SQLReviewPopover: View { editorView } } - .padding(ThemeEngine.shared.activeTheme.spacing.md) + .padding(16) .frame(width: 520, height: contentHeight) .onExitCommand { dismiss() @@ -101,18 +101,18 @@ struct SQLReviewPopover: View { private var headerView: some View { HStack { Text("\(PluginManager.shared.queryLanguageName(for: databaseType)) Preview") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) if !statements.isEmpty { Text( "(\(statements.count) \(statements.count == 1 ? String(localized: "statement") : String(localized: "statements")))" ) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } Spacer() if !statements.isEmpty { Button(action: copyAllToClipboard) { - HStack(spacing: ThemeEngine.shared.activeTheme.spacing.xxs) { + HStack(spacing: 4) { Image(systemName: copied ? "checkmark" : "doc.on.doc") Text(copied ? String(localized: "Copied!") : String(localized: "Copy All")) } @@ -126,13 +126,13 @@ struct SQLReviewPopover: View { // MARK: - Empty State private var emptyState: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.xs) { + VStack(spacing: 8) { Spacer() Image(systemName: "doc.plaintext") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.huge)) + .font(.system(size: 32)) .foregroundStyle(.tertiary) Text(String(localized: "No pending changes")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) Spacer() } @@ -150,17 +150,17 @@ struct SQLReviewPopover: View { configuration: Self.makeConfiguration(), state: $editorState ) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium) + RoundedRectangle(cornerRadius: 6) .stroke(Color(nsColor: .separatorColor), lineWidth: 0.5) ) } else { // Lightweight placeholder while SourceEditor loads Color(nsColor: .textBackgroundColor) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium) + RoundedRectangle(cornerRadius: 6) .stroke(Color(nsColor: .separatorColor), lineWidth: 0.5) ) } @@ -173,7 +173,7 @@ struct SQLReviewPopover: View { appearance: .init( theme: TableProEditorTheme.make(), font: NSFont.monospacedSystemFont( - ofSize: ThemeEngine.shared.activeTheme.typography.medium, weight: .regular), + ofSize: 12, weight: .regular), wrapLines: true ), behavior: .init( diff --git a/TablePro/Views/Components/SearchFieldView.swift b/TablePro/Views/Components/SearchFieldView.swift index 8384fb90e..0b55b5c33 100644 --- a/TablePro/Views/Components/SearchFieldView.swift +++ b/TablePro/Views/Components/SearchFieldView.swift @@ -11,7 +11,7 @@ struct SearchFieldView: View { var fontSize: CGFloat? var body: some View { - let resolvedSize = fontSize ?? ThemeEngine.shared.activeTheme.typography.body + let resolvedSize = fontSize ?? 13 HStack(spacing: 6) { Image(systemName: "magnifyingglass") .font(.system(size: resolvedSize)) @@ -32,6 +32,6 @@ struct SearchFieldView: View { .padding(.horizontal, 8) .padding(.vertical, 6) .background(Color(nsColor: .controlBackgroundColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .clipShape(RoundedRectangle(cornerRadius: 6)) } } diff --git a/TablePro/Views/Components/SectionHeaderView.swift b/TablePro/Views/Components/SectionHeaderView.swift index de859a76a..b32375472 100644 --- a/TablePro/Views/Components/SectionHeaderView.swift +++ b/TablePro/Views/Components/SectionHeaderView.swift @@ -45,28 +45,28 @@ struct SectionHeaderView: View { } private var headerContent: some View { - HStack(spacing: ThemeEngine.shared.activeTheme.spacing.xs) { + HStack(spacing: 8) { if isCollapsible { Image(systemName: "chevron.right") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .semibold)) + .font(.caption.weight(.semibold)) .foregroundStyle(ThemeEngine.shared.colors.ui.tertiaryTextSwiftUI) .rotationEffect(.degrees(isExpanded ? 90 : 0)) - .animation(.easeInOut(duration: ThemeEngine.shared.activeTheme.animations.normal), value: isExpanded) + .animation(.easeInOut(duration: 0.15), value: isExpanded) } if let icon = icon { Image(systemName: icon) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(ThemeEngine.shared.colors.ui.secondaryTextSwiftUI) } Text(title) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) .foregroundStyle(ThemeEngine.shared.colors.ui.primaryTextSwiftUI) if let count = count { Text("(\(count))") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(ThemeEngine.shared.colors.ui.tertiaryTextSwiftUI) } @@ -74,14 +74,14 @@ struct SectionHeaderView: View { actions() } - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.sm) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xs) + .padding(.horizontal, 12) + .padding(.vertical, 8) .background( isCollapsible ? ThemeEngine.shared.colors.ui.controlBackgroundSwiftUI.opacity(0.5) : Color.clear ) - .cornerRadius(ThemeEngine.shared.activeTheme.cornerRadius.medium) + .cornerRadius(6) .contentShape(Rectangle()) } } diff --git a/TablePro/Views/Components/SyncStatusIndicator.swift b/TablePro/Views/Components/SyncStatusIndicator.swift index 7d903798e..82642011c 100644 --- a/TablePro/Views/Components/SyncStatusIndicator.swift +++ b/TablePro/Views/Components/SyncStatusIndicator.swift @@ -24,7 +24,7 @@ struct SyncStatusIndicator: View { Text(statusLabel) .contentTransition(.numericText()) } - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(foregroundStyle) .animation(.default, value: syncCoordinator.syncStatus) } diff --git a/TablePro/Views/Connection/ConnectionColorPicker.swift b/TablePro/Views/Connection/ConnectionColorPicker.swift index 5f2e718e2..263086ba4 100644 --- a/TablePro/Views/Connection/ConnectionColorPicker.swift +++ b/TablePro/Views/Connection/ConnectionColorPicker.swift @@ -39,20 +39,20 @@ private struct ColorDot: View { // "None" option - shows as crossed circle Circle() .stroke(Color.secondary, lineWidth: 1) - .frame(width: ThemeEngine.shared.activeTheme.iconSizes.large, height: ThemeEngine.shared.activeTheme.iconSizes.large) + .frame(width: 20, height: 20) Image(systemName: "circle.slash") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.small)) + .font(.system(size: 12)) .foregroundStyle(.secondary) } else { Circle() .fill(color.color) - .frame(width: ThemeEngine.shared.activeTheme.iconSizes.large, height: ThemeEngine.shared.activeTheme.iconSizes.large) + .frame(width: 20, height: 20) } if isSelected { Circle() .stroke(Color.primary, lineWidth: 2) - .frame(width: ThemeEngine.shared.activeTheme.iconSizes.extraLarge, height: ThemeEngine.shared.activeTheme.iconSizes.extraLarge) + .frame(width: 24, height: 24) } } .frame(width: 28, height: 28) diff --git a/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift b/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift index 27837ce6a..5dfe5501f 100644 --- a/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift +++ b/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift @@ -30,7 +30,7 @@ struct ConnectionExportOptionsSheet: View { var body: some View { VStack(alignment: .leading, spacing: 16) { Text(String(localized: "Export Options")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) .frame(maxWidth: .infinity, alignment: .leading) VStack(alignment: .leading, spacing: 8) { @@ -41,7 +41,7 @@ struct ConnectionExportOptionsSheet: View { if !isProAvailable { Text("Pro") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .medium)) + .font(.caption.weight(.medium)) .foregroundStyle(.white) .padding(.horizontal, 5) .padding(.vertical, 1) @@ -54,7 +54,7 @@ struct ConnectionExportOptionsSheet: View { if includeCredentials { Text("Passwords will be encrypted with the passphrase you provide.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) SecureField("Passphrase (8+ characters)", text: $passphrase) @@ -65,7 +65,7 @@ struct ConnectionExportOptionsSheet: View { if !passphrase.isEmpty && !confirmPassphrase.isEmpty && passphrase != confirmPassphrase { Text("Passphrases do not match") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemRed)) } } diff --git a/TablePro/Views/Connection/ConnectionGroupPicker.swift b/TablePro/Views/Connection/ConnectionGroupPicker.swift index 67e61f209..8b5048637 100644 --- a/TablePro/Views/Connection/ConnectionGroupPicker.swift +++ b/TablePro/Views/Connection/ConnectionGroupPicker.swift @@ -240,13 +240,13 @@ private struct GroupColorPicker: View { Button(action: { selectedColor = color }) { Circle() .fill(color == .none ? Color(nsColor: .quaternaryLabelColor) : color.color) - .frame(width: ThemeEngine.shared.activeTheme.iconSizes.medium, height: ThemeEngine.shared.activeTheme.iconSizes.medium) + .frame(width: 16, height: 16) .overlay( Circle() .stroke(Color.primary, lineWidth: selectedColor == color ? 2 : 0) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.large, - height: ThemeEngine.shared.activeTheme.iconSizes.large + width: 20, + height: 20 ) ) } diff --git a/TablePro/Views/Connection/ConnectionImportSheet.swift b/TablePro/Views/Connection/ConnectionImportSheet.swift index ae5d70231..bd2397345 100644 --- a/TablePro/Views/Connection/ConnectionImportSheet.swift +++ b/TablePro/Views/Connection/ConnectionImportSheet.swift @@ -83,9 +83,9 @@ struct ConnectionImportSheet: View { private func header(_ preview: ConnectionImportPreview) -> some View { HStack { Text(String(localized: "Import Connections")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) Text("(\(fileURL.lastPathComponent))") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) Spacer() Toggle(String(localized: "Select All"), isOn: Binding( @@ -139,11 +139,11 @@ struct ConnectionImportSheet: View { VStack(alignment: .leading, spacing: 1) { HStack(spacing: 4) { Text(item.connection.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .lineLimit(1) if case .duplicate = item.status { Text(String(localized: "duplicate")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption)) + .font(.caption) .foregroundStyle(.secondary) .padding(.horizontal, 4) .padding(.vertical, 1) @@ -157,7 +157,7 @@ struct ConnectionImportSheet: View { Text("\(item.connection.host):\(String(item.connection.port))") warningText(for: item.status) } - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) } @@ -191,11 +191,11 @@ struct ConnectionImportSheet: View { switch status { case .ready: Image(systemName: "checkmark.circle.fill") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(Color(nsColor: .systemGreen)) case .warnings: Image(systemName: "exclamationmark.triangle.fill") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(Color(nsColor: .systemYellow)) case .duplicate: EmptyView() @@ -221,10 +221,10 @@ struct ConnectionImportSheet: View { .foregroundStyle(.secondary) Text("This file is encrypted") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) Text("Enter the passphrase to decrypt and import connections.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -235,7 +235,7 @@ struct ConnectionImportSheet: View { if let passphraseError { Text(passphraseError) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemRed)) } @@ -260,7 +260,7 @@ struct ConnectionImportSheet: View { private func footer(_ preview: ConnectionImportPreview) -> some View { HStack { Text("\(selectedIds.count) of \(preview.items.count) selected") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) Spacer() diff --git a/TablePro/Views/Connection/ConnectionSidebarHeader.swift b/TablePro/Views/Connection/ConnectionSidebarHeader.swift index 4ee731865..2d803316a 100644 --- a/TablePro/Views/Connection/ConnectionSidebarHeader.swift +++ b/TablePro/Views/Connection/ConnectionSidebarHeader.swift @@ -92,17 +92,17 @@ struct ConnectionSidebarHeader: View { if let session = currentSession { session.connection.type.iconImage .renderingMode(.template) - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.medium)) + .font(.system(size: 16)) .foregroundStyle(session.connection.displayColor) } else { Image(systemName: "cylinder") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.medium)) + .font(.system(size: 16)) .foregroundStyle(.secondary) } // Connection name Text(currentSession?.connection.name ?? "No Connection") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) .lineLimit(1) Spacer() @@ -113,17 +113,17 @@ struct ConnectionSidebarHeader: View { Circle() .fill(statusColor(for: session)) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.tinyDot, - height: ThemeEngine.shared.activeTheme.iconSizes.tinyDot) + width: 6, + height: 6) } Image(systemName: "chevron.down") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny, weight: .medium)) + .font(.system(size: 9, weight: .medium)) .foregroundStyle(.secondary) } } .padding(.horizontal, 12) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.sm) + .padding(.vertical, 12) .contentShape(Rectangle()) } .buttonStyle(.plain) @@ -144,8 +144,8 @@ struct ConnectionSidebarHeader: View { Circle() .fill(statusColor(for: session)) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.tinyDot, - height: ThemeEngine.shared.activeTheme.iconSizes.tinyDot) + width: 6, + height: 6) if case .connecting = session.status { ProgressView() diff --git a/TablePro/Views/Connection/ConnectionTagEditor.swift b/TablePro/Views/Connection/ConnectionTagEditor.swift index ccd3359bc..b44460c2a 100644 --- a/TablePro/Views/Connection/ConnectionTagEditor.swift +++ b/TablePro/Views/Connection/ConnectionTagEditor.swift @@ -186,13 +186,13 @@ private struct TagColorPicker: View { Button(action: { selectedColor = color }) { Circle() .fill(color.color) - .frame(width: ThemeEngine.shared.activeTheme.iconSizes.medium, height: ThemeEngine.shared.activeTheme.iconSizes.medium) + .frame(width: 16, height: 16) .overlay( Circle() .stroke(Color.primary, lineWidth: selectedColor == color ? 2 : 0) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.large, - height: ThemeEngine.shared.activeTheme.iconSizes.large + width: 20, + height: 20 ) ) } diff --git a/TablePro/Views/Connection/OnboardingContentView.swift b/TablePro/Views/Connection/OnboardingContentView.swift index c4b0286c1..6b17bb30a 100644 --- a/TablePro/Views/Connection/OnboardingContentView.swift +++ b/TablePro/Views/Connection/OnboardingContentView.swift @@ -86,7 +86,7 @@ struct OnboardingContentView: View { // MARK: - Welcome Page private var welcomePage: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.md) { + VStack(spacing: 16) { Image(nsImage: NSApp.applicationIconImage) .resizable() .frame(width: 80, height: 80) @@ -95,7 +95,7 @@ struct OnboardingContentView: View { .font(.system(size: 24, weight: .bold, design: .rounded)) Text("A fast, lightweight native macOS database client") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) } } @@ -103,9 +103,9 @@ struct OnboardingContentView: View { // MARK: - Features Page private var featuresPage: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.xl) { + VStack(spacing: 24) { Text("What you can do") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title2, weight: .semibold)) + .font(.title2.weight(.semibold)) VStack(alignment: .leading, spacing: 16) { featureRow( @@ -142,15 +142,15 @@ struct OnboardingContentView: View { private func featureRow(icon: String, title: String, description: String) -> some View { HStack(spacing: 16) { Image(systemName: icon) - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge)) + .font(.system(size: 24)) .foregroundStyle(.tint) .frame(width: 40) - VStack(alignment: .leading, spacing: ThemeEngine.shared.activeTheme.spacing.xxxs) { + VStack(alignment: .leading, spacing: 2) { Text(title) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) Text(description) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } } @@ -159,7 +159,7 @@ struct OnboardingContentView: View { // MARK: - Get Started Page private var getStartedPage: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.md) { + VStack(spacing: 16) { Image(systemName: "checkmark.circle.fill") .font(.system(size: 48)) .foregroundStyle(Color(nsColor: .systemGreen)) @@ -168,7 +168,7 @@ struct OnboardingContentView: View { .font(.system(size: 22, weight: .bold, design: .rounded)) Text("Create a connection to get started with\nyour databases.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } @@ -223,8 +223,8 @@ struct OnboardingContentView: View { .animation(.easeInOut(duration: 0.25), value: currentPage) .frame(minWidth: 110, alignment: .trailing) } - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.xl) - .padding(.bottom, ThemeEngine.shared.activeTheme.spacing.lg) + .padding(.horizontal, 24) + .padding(.bottom, 20) } // MARK: - Actions diff --git a/TablePro/Views/Connection/WelcomeConnectionRow.swift b/TablePro/Views/Connection/WelcomeConnectionRow.swift index c95104313..f35f147b3 100644 --- a/TablePro/Views/Connection/WelcomeConnectionRow.swift +++ b/TablePro/Views/Connection/WelcomeConnectionRow.swift @@ -20,33 +20,33 @@ struct WelcomeConnectionRow: View { HStack(spacing: 12) { connection.type.iconImage .renderingMode(.template) - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.medium)) + .font(.system(size: 16)) .foregroundStyle(connection.displayColor) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.medium, - height: ThemeEngine.shared.activeTheme.iconSizes.medium + width: 16, + height: 16 ) VStack(alignment: .leading, spacing: 2) { HStack(spacing: 6) { Text(connection.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) .foregroundStyle(.primary) if let tag = displayTag { Text(tag.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny)) + .font(.system(size: 9)) .foregroundStyle(tag.color.color) - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.xxs) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xxxs) + .padding(.horizontal, 4) + .padding(.vertical, 2) .background( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small).fill( + RoundedRectangle(cornerRadius: 4).fill( tag.color.color.opacity(0.15))) } } Text(connectionSubtitle) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) .help(connectionSubtitle) @@ -54,7 +54,7 @@ struct WelcomeConnectionRow: View { Spacer() } - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xxs) + .padding(.vertical, 4) .contentShape(Rectangle()) .overlay( DoubleClickView { onConnect?() } diff --git a/TablePro/Views/Connection/WelcomeLeftPanel.swift b/TablePro/Views/Connection/WelcomeLeftPanel.swift index ded48f6f6..412f18be3 100644 --- a/TablePro/Views/Connection/WelcomeLeftPanel.swift +++ b/TablePro/Views/Connection/WelcomeLeftPanel.swift @@ -23,21 +23,21 @@ struct WelcomeLeftPanel: View { Text("TablePro") .font( .system( - size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge, weight: .semibold, + size: 24, weight: .semibold, design: .rounded)) Text("Version \(Bundle.main.appVersion)") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) if LicenseManager.shared.status.isValid { Label("Pro", systemImage: "checkmark.seal.fill") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(Color(nsColor: .systemGreen)) } else { Button(action: onActivateLicense) { Text("Activate License") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) } .buttonStyle(.plain) .foregroundStyle(.secondary) @@ -57,7 +57,7 @@ struct WelcomeLeftPanel: View { Label("Sponsor TablePro", systemImage: "heart") } .buttonStyle(.plain) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.pink) Button(action: onCreateConnection) { @@ -82,10 +82,10 @@ struct WelcomeLeftPanel: View { KeyboardHint(keys: "⌘,", label: nil) } } - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.tertiary) - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.sm) - .padding(.bottom, ThemeEngine.shared.activeTheme.spacing.lg) + .padding(.horizontal, 12) + .padding(.bottom, 20) } .frame(width: 260) } @@ -94,13 +94,13 @@ struct WelcomeLeftPanel: View { struct WelcomeButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.primary) - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.md) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.sm) + .padding(.horizontal, 16) + .padding(.vertical, 12) .frame(maxWidth: .infinity, alignment: .leading) .background( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.large) + RoundedRectangle(cornerRadius: 8) .fill( Color( nsColor: configuration.isPressed @@ -116,9 +116,9 @@ struct KeyboardHint: View { var body: some View { HStack(spacing: 4) { Text(keys) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, design: .monospaced)) - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.xxs + 1) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xxxs) + .font(.system(.caption, design: .monospaced)) + .padding(.horizontal, 5) + .padding(.vertical, 2) .background( RoundedRectangle(cornerRadius: 3) .fill(Color(nsColor: .quaternaryLabelColor)) diff --git a/TablePro/Views/Connection/WelcomeWindowView.swift b/TablePro/Views/Connection/WelcomeWindowView.swift index c91a2e00a..ed1e205bc 100644 --- a/TablePro/Views/Connection/WelcomeWindowView.swift +++ b/TablePro/Views/Connection/WelcomeWindowView.swift @@ -120,11 +120,11 @@ struct WelcomeWindowView: View { HStack(spacing: 8) { Button(action: { openWindow(id: "connection-form") }) { Image(systemName: "plus") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium)) + .font(.callout.weight(.medium)) .foregroundStyle(.secondary) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.extraLarge, - height: ThemeEngine.shared.activeTheme.iconSizes.extraLarge + width: 24, + height: 24 ) .background( RoundedRectangle(cornerRadius: 6) @@ -136,11 +136,11 @@ struct WelcomeWindowView: View { Button(action: { vm.pendingMoveToNewGroup = []; vm.activeSheet = .newGroup(parentId: nil) }) { Image(systemName: "folder.badge.plus") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium)) + .font(.callout.weight(.medium)) .foregroundStyle(.secondary) .frame( - width: ThemeEngine.shared.activeTheme.iconSizes.extraLarge, - height: ThemeEngine.shared.activeTheme.iconSizes.extraLarge + width: 24, + height: 24 ) .background( RoundedRectangle(cornerRadius: 6) @@ -152,12 +152,12 @@ struct WelcomeWindowView: View { HStack(spacing: 6) { Image(systemName: "magnifyingglass") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) TextField("Search for connection...", text: $vm.searchText) .textFieldStyle(.plain) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .focused($focus, equals: .search) .onKeyPress(.return) { vm.connectSelectedConnections() @@ -201,15 +201,15 @@ struct WelcomeWindowView: View { return .handled } } - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.sm) + .padding(.horizontal, 12) .padding(.vertical, 6) .background( RoundedRectangle(cornerRadius: 8) .fill(Color(nsColor: .quaternaryLabelColor)) ) } - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.md) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.sm) + .padding(.horizontal, 16) + .padding(.vertical, 12) Divider() @@ -350,7 +350,7 @@ struct WelcomeWindowView: View { onConnect: { vm.connectToDatabase(connection) } ) .tag(connection.id) - .listRowInsets(ThemeEngine.shared.activeTheme.spacing.listRowInsets.swiftUI) + .listRowInsets(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .listRowSeparator(.hidden) .contextMenu { contextMenuContent(for: connection) } } @@ -374,8 +374,8 @@ struct WelcomeWindowView: View { .lineLimit(1) } } - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xxs) - .listRowInsets(ThemeEngine.shared.activeTheme.spacing.listRowInsets.swiftUI) + .padding(.vertical, 4) + .listRowInsets(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .contentShape(Rectangle()) .simultaneousGesture(TapGesture(count: 2).onEnded { vm.connectToLinkedConnection(linked) @@ -401,11 +401,11 @@ struct WelcomeWindowView: View { } Text(group.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .semibold)) + .font(.subheadline.weight(.semibold)) .foregroundStyle(.secondary) Text("\(connectionCount(in: group.id, connections: vm.connections, groups: vm.groups))") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny)) + .font(.system(size: 9)) .foregroundStyle(.tertiary) Spacer() @@ -517,30 +517,30 @@ struct WelcomeWindowView: View { Spacer() Image(systemName: "cylinder.split.1x2") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.huge)) + .font(.system(size: 32)) .foregroundStyle(.tertiary) if vm.searchText.isEmpty { Text("No Connections") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .medium)) + .font(.title3.weight(.medium)) .foregroundStyle(.secondary) Text("Create a connection to get started") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) Button(action: { openWindow(id: "connection-form") }) { Label("New Connection", systemImage: "plus") } .controlSize(.large) - .padding(.top, ThemeEngine.shared.activeTheme.spacing.xxs) + .padding(.top, 4) } else { Text("No Matching Connections") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .medium)) + .font(.title3.weight(.medium)) .foregroundStyle(.secondary) Text("Try a different search term") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) } diff --git a/TablePro/Views/DatabaseSwitcher/CreateDatabaseSheet.swift b/TablePro/Views/DatabaseSwitcher/CreateDatabaseSheet.swift index 50e42d8cb..c8c1c2e97 100644 --- a/TablePro/Views/DatabaseSwitcher/CreateDatabaseSheet.swift +++ b/TablePro/Views/DatabaseSwitcher/CreateDatabaseSheet.swift @@ -34,7 +34,7 @@ struct CreateDatabaseSheet: View { VStack(spacing: 0) { // Header Text("Create Database") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) .padding(.vertical, 12) Divider() @@ -44,19 +44,19 @@ struct CreateDatabaseSheet: View { // Database name VStack(alignment: .leading, spacing: 6) { Text("Database Name") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) TextField("Enter database name", text: $databaseName) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) } if config.showOptions { // Charset / Encoding VStack(alignment: .leading, spacing: 6) { Text(config.charsetLabel) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) Picker("", selection: $charset) { @@ -66,13 +66,13 @@ struct CreateDatabaseSheet: View { } .labelsHidden() .pickerStyle(.menu) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) } // Collation / LC_COLLATE VStack(alignment: .leading, spacing: 6) { Text(config.collationLabel) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) Picker("", selection: $collation) { @@ -82,14 +82,14 @@ struct CreateDatabaseSheet: View { } .labelsHidden() .pickerStyle(.menu) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) } } // Error message if let error = errorMessage { Text(error) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemRed)) } } diff --git a/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift b/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift index 3c0839ec4..aeeccd6fe 100644 --- a/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift +++ b/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift @@ -68,7 +68,7 @@ struct DatabaseSwitcherSheet: View { Text(isSchemaMode ? String(localized: "Open Schema") : String(localized: "Open Database")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) .padding(.vertical, 12) // Databases / Schemas toggle (PostgreSQL only) @@ -273,7 +273,7 @@ struct DatabaseSwitcherSheet: View { .fill(isSelected ? Color(nsColor: .selectedContentBackgroundColor) : Color.clear) .padding(.horizontal, 4) ) - .listRowInsets(ThemeEngine.shared.activeTheme.spacing.listRowInsets.swiftUI) + .listRowInsets(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .listRowSeparator(.hidden) .id(database.name) .tag(database.name) @@ -306,7 +306,7 @@ struct DatabaseSwitcherSheet: View { Text(isSchemaMode ? String(localized: "Loading schemas...") : String(localized: "Loading databases...")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -315,16 +315,16 @@ struct DatabaseSwitcherSheet: View { private func errorView(_ message: String) -> some View { VStack(spacing: 12) { Image(systemName: "exclamationmark.triangle") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge)) + .font(.system(size: 24)) .foregroundStyle(.orange) Text(isSchemaMode ? String(localized: "Failed to load schemas") : String(localized: "Failed to load databases")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) Text(message) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal) @@ -341,16 +341,16 @@ struct DatabaseSwitcherSheet: View { private var sqliteEmptyState: some View { VStack(spacing: 12) { Image(systemName: "doc.fill") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge)) + .font(.system(size: 24)) .foregroundStyle(.secondary) Text("SQLite is file-based") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) Text( "Each SQLite file is a separate database.\nTo open a different database, create a new connection." ) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal) @@ -361,24 +361,24 @@ struct DatabaseSwitcherSheet: View { private var emptyState: some View { VStack(spacing: 12) { Image(systemName: "magnifyingglass") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge)) + .font(.system(size: 24)) .foregroundStyle(.secondary) if viewModel.searchText.isEmpty { Text(isSchemaMode ? String(localized: "No schemas found") : String(localized: "No databases found")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) } else { Text(isSchemaMode ? String(localized: "No matching schemas") : String(localized: "No matching databases")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) Text(isSchemaMode ? String(format: String(localized: "No schemas match \"%@\""), viewModel.searchText) : String(format: String(localized: "No databases match \"%@\""), viewModel.searchText)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } } diff --git a/TablePro/Views/DatabaseSwitcher/DropDatabaseSheet.swift b/TablePro/Views/DatabaseSwitcher/DropDatabaseSheet.swift index 360e8ad1d..e8f271906 100644 --- a/TablePro/Views/DatabaseSwitcher/DropDatabaseSheet.swift +++ b/TablePro/Views/DatabaseSwitcher/DropDatabaseSheet.swift @@ -21,7 +21,7 @@ struct DropDatabaseSheet: View { VStack(spacing: 0) { // Header Text("Drop Database") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) .padding(.vertical, 12) Divider() @@ -33,17 +33,17 @@ struct DropDatabaseSheet: View { .foregroundStyle(.red) Text(String(format: String(localized: "Drop database '%@'?"), databaseName)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) .multilineTextAlignment(.center) Text(String(localized: "All tables and data will be permanently deleted.")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) if let error = errorMessage { Text(error) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemRed)) .multilineTextAlignment(.center) } diff --git a/TablePro/Views/Editor/HistoryPanelView.swift b/TablePro/Views/Editor/HistoryPanelView.swift index 23e9a7ab6..38816ac6b 100644 --- a/TablePro/Views/Editor/HistoryPanelView.swift +++ b/TablePro/Views/Editor/HistoryPanelView.swift @@ -110,7 +110,7 @@ private extension HistoryPanelView { .contextMenu { contextMenu(for: entry) } } .listStyle(.plain) - .environment(\.defaultMinListRowHeight, ThemeEngine.shared.activeTheme.rowHeights.comfortable) + .environment(\.defaultMinListRowHeight, 44) .onDeleteCommand { deleteSelectedEntry() } @@ -154,10 +154,10 @@ private extension HistoryPanelView { .foregroundStyle(.tertiary) .accessibilityHidden(true) Text("No Matching Queries") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) .foregroundStyle(.secondary) Text("Try adjusting your search terms\nor date filter.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) .multilineTextAlignment(.center) } else { @@ -166,10 +166,10 @@ private extension HistoryPanelView { .foregroundStyle(.tertiary) .accessibilityHidden(true) Text("No Query History Yet") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) .foregroundStyle(.secondary) Text("Your executed queries will\nappear here for quick access.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) .multilineTextAlignment(.center) } @@ -229,10 +229,10 @@ private extension HistoryPanelView { // Metadata VStack(alignment: .leading, spacing: 4) { Text(buildPrimaryMetadata(entry)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) Text(buildSecondaryMetadata(entry)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.tertiary) } .frame(maxWidth: .infinity, alignment: .leading) @@ -269,10 +269,10 @@ private extension HistoryPanelView { .foregroundStyle(.tertiary) .accessibilityHidden(true) Text("Select a Query") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .medium)) + .font(.title3.weight(.medium)) .foregroundStyle(.secondary) Text("Choose a query from the list\nto see its full content here.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) .multilineTextAlignment(.center) } @@ -414,25 +414,25 @@ private struct HistoryRowSwiftUI: View { HStack(spacing: 8) { Image(systemName: entry.wasSuccessful ? "checkmark.circle.fill" : "xmark.circle.fill") .foregroundStyle(entry.wasSuccessful ? Color(nsColor: .systemGreen) : Color(nsColor: .systemRed)) - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.default)) + .font(.system(size: 14)) VStack(alignment: .leading, spacing: 2) { Text(entry.queryPreview) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .lineLimit(1) Text(entry.databaseName) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) HStack { Text(relativeTime(entry.executedAt)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.tertiary) Spacer() Text(entry.formattedExecutionTime) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.tertiary) } } diff --git a/TablePro/Views/Editor/VimModeIndicatorView.swift b/TablePro/Views/Editor/VimModeIndicatorView.swift index 2ca3f33d2..8d3d87c60 100644 --- a/TablePro/Views/Editor/VimModeIndicatorView.swift +++ b/TablePro/Views/Editor/VimModeIndicatorView.swift @@ -19,7 +19,7 @@ struct VimModeIndicatorView: View { .padding(.horizontal, 6) .padding(.vertical, 2) .background(backgroundColor) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) } else { Text(mode.displayLabel) .font(.system(size: 10, weight: .semibold, design: .monospaced)) @@ -27,7 +27,7 @@ struct VimModeIndicatorView: View { .padding(.horizontal, 6) .padding(.vertical, 2) .background(backgroundColor) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) } } diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index c4934b1cf..c3f4abb78 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -205,7 +205,7 @@ struct ExportDialog: View { // Header with title and selection count HStack { Text("Items") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) Spacer() @@ -213,7 +213,7 @@ struct ExportDialog: View { if let plugin = currentPlugin { ForEach(type(of: plugin).perTableOptionColumns) { column in Text(column.label) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) .frame(width: column.width, alignment: .center) } @@ -232,7 +232,7 @@ struct ExportDialog: View { ProgressView() .scaleEffect(0.8) Text("Loading databases...") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .padding(.top, 8) Spacer() @@ -257,7 +257,7 @@ struct ExportDialog: View { HStack { Spacer() Text("No export formats available. Enable export plugins in Settings > Plugins.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) Spacer() } @@ -286,7 +286,7 @@ struct ExportDialog: View { let description = formatDescription(for: config.formatId) if !description.isEmpty { Text(description) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } } @@ -295,29 +295,29 @@ struct ExportDialog: View { VStack(spacing: 2) { if isProGatedFormat(config.formatId) { Text(String(localized: "XLSX export requires a Pro license.")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemOrange)) Button(String(localized: "Activate License...")) { showActivationSheet = true } - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .buttonStyle(.link) } else if case .streamingQuery = mode { Text("All rows") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } else if isQueryResultsMode { Text("\(queryResultsRowCount) row\(queryResultsRowCount == 1 ? "" : "s") to export") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } else { Text("\(exportableCount) table\(exportableCount == 1 ? "" : "s") to export") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) if let plugin = currentPlugin, !type(of: plugin).perTableOptionColumns.isEmpty, exportableCount < selectedCount { Text("\(selectedCount - exportableCount) skipped (no options)") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemOrange)) } } @@ -349,17 +349,17 @@ struct ExportDialog: View { // File name section VStack(alignment: .leading, spacing: 6) { Text("File name") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) HStack(spacing: 4) { TextField("export", text: $config.fileName) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) Text(".\(fileExtension)") .foregroundStyle(.secondary) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, design: .monospaced)) + .font(.system(.body, design: .monospaced)) .lineLimit(1) .fixedSize() } @@ -367,7 +367,7 @@ struct ExportDialog: View { // Show validation error if filename is invalid if let validationError = fileNameValidationError { Text(validationError) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemRed)) } } @@ -392,7 +392,7 @@ struct ExportDialog: View { .scaleEffect(0.7) Text(exportService?.state.currentTable ?? "") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.middle) diff --git a/TablePro/Views/Export/ExportProgressView.swift b/TablePro/Views/Export/ExportProgressView.swift index a856be0e0..040d9102c 100644 --- a/TablePro/Views/Export/ExportProgressView.swift +++ b/TablePro/Views/Export/ExportProgressView.swift @@ -24,7 +24,7 @@ struct ExportProgressView: View { Text(totalTables > 1 ? String(localized: "Export multiple tables") : String(localized: "Export table")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) // Table info and row count VStack(spacing: 8) { @@ -32,11 +32,11 @@ struct ExportProgressView: View { // Show status message if set, otherwise show table name if !statusMessage.isEmpty { Text(statusMessage) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) } else { Text("\(tableName) (\(tableIndex)/\(totalTables))") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .lineLimit(1) .truncationMode(.middle) } @@ -45,7 +45,7 @@ struct ExportProgressView: View { if statusMessage.isEmpty { Text("\(processedRows.formatted())/\(totalRows.formatted()) rows") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, design: .monospaced)) + .font(.system(.body, design: .monospaced)) .foregroundStyle(.secondary) } } diff --git a/TablePro/Views/Export/ExportSuccessView.swift b/TablePro/Views/Export/ExportSuccessView.swift index fe430efdd..56be0a5ed 100644 --- a/TablePro/Views/Export/ExportSuccessView.swift +++ b/TablePro/Views/Export/ExportSuccessView.swift @@ -30,10 +30,10 @@ struct ExportSuccessView: View { // Title and message VStack(spacing: 6) { Text("Success") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) Text("Export completed successfully") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) } @@ -57,7 +57,7 @@ struct ExportSuccessView: View { // Don't show again checkbox Toggle("Don't show this again", isOn: $localDontShowAgain) .toggleStyle(.checkbox) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } .padding(24) diff --git a/TablePro/Views/Export/ExportTableTreeView.swift b/TablePro/Views/Export/ExportTableTreeView.swift index b433e2b84..7ecfd79d5 100644 --- a/TablePro/Views/Export/ExportTableTreeView.swift +++ b/TablePro/Views/Export/ExportTableTreeView.swift @@ -71,10 +71,10 @@ struct ExportTableTreeView: View { Image(systemName: "cylinder") .foregroundStyle(.blue) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) Text(database.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .lineLimit(1) .truncationMode(.middle) } @@ -107,10 +107,10 @@ struct ExportTableTreeView: View { Image(systemName: table.wrappedValue.type == .view ? "eye" : "tablecells") .foregroundStyle(table.wrappedValue.type == .view ? .purple : .gray) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) Text(table.wrappedValue.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .lineLimit(1) .truncationMode(.middle) diff --git a/TablePro/Views/Filter/CompletionTextField.swift b/TablePro/Views/Filter/CompletionTextField.swift index e4b24e9dc..18bb2df15 100644 --- a/TablePro/Views/Filter/CompletionTextField.swift +++ b/TablePro/Views/Filter/CompletionTextField.swift @@ -21,7 +21,7 @@ struct CompletionTextField: NSViewRepresentable { textField.placeholderString = placeholder textField.bezelStyle = .roundedBezel textField.controlSize = .small - textField.font = .systemFont(ofSize: ThemeEngine.shared.activeTheme.typography.medium) + textField.font = .systemFont(ofSize: 12) textField.delegate = context.coordinator textField.stringValue = text textField.completionItems = completions diff --git a/TablePro/Views/Filter/FilterPanelView.swift b/TablePro/Views/Filter/FilterPanelView.swift index 95fd08a3e..08986b58a 100644 --- a/TablePro/Views/Filter/FilterPanelView.swift +++ b/TablePro/Views/Filter/FilterPanelView.swift @@ -56,7 +56,7 @@ struct FilterPanelView: View { private var filterHeader: some View { HStack(spacing: 8) { Text("Filters") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium)) + .font(.callout.weight(.medium)) if filterState.filters.count > 1 { Picker("", selection: $filterState.filterLogicMode) { @@ -91,7 +91,7 @@ struct FilterPanelView: View { .help(String(localized: "Apply filters")) } .padding(.horizontal, 12) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xs) + .padding(.vertical, 8) .background(Color(nsColor: .controlBackgroundColor)) .contentShape(Rectangle()) .alert(String(localized: "Save Filter Preset"), isPresented: $showSavePresetAlert) { diff --git a/TablePro/Views/Filter/FilterRowView.swift b/TablePro/Views/Filter/FilterRowView.swift index cd5112bea..48941e34f 100644 --- a/TablePro/Views/Filter/FilterRowView.swift +++ b/TablePro/Views/Filter/FilterRowView.swift @@ -112,7 +112,7 @@ struct FilterRowView: View { if filter.filterOperator.requiresSecondValue { Text("and") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) TextField("Value", text: Binding( @@ -121,14 +121,14 @@ struct FilterRowView: View { )) .textFieldStyle(.roundedBorder) .controlSize(.small) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .frame(minWidth: 80) .accessibilityLabel(String(localized: "Second filter value")) .onSubmit { onSubmit() } } } else { Text("—") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.tertiary) .frame(minWidth: 80, alignment: .leading) } diff --git a/TablePro/Views/Filter/MixedStateCheckbox.swift b/TablePro/Views/Filter/MixedStateCheckbox.swift index 75d7d416e..f62a41d2b 100644 --- a/TablePro/Views/Filter/MixedStateCheckbox.swift +++ b/TablePro/Views/Filter/MixedStateCheckbox.swift @@ -16,7 +16,7 @@ struct MixedStateCheckbox: NSViewRepresentable { func makeNSView(context: Context) -> NSButton { let button = NSButton(checkboxWithTitle: title, target: context.coordinator, action: #selector(Coordinator.didToggle(_:))) button.allowsMixedState = true - button.font = NSFont.systemFont(ofSize: ThemeEngine.shared.activeTheme.typography.small) + button.font = NSFont.systemFont(ofSize: 11) button.setContentHuggingPriority(.defaultHigh, for: .horizontal) return button } @@ -24,7 +24,7 @@ struct MixedStateCheckbox: NSViewRepresentable { func updateNSView(_ button: NSButton, context: Context) { button.title = title button.state = state - button.font = NSFont.systemFont(ofSize: ThemeEngine.shared.activeTheme.typography.small) + button.font = NSFont.systemFont(ofSize: 11) context.coordinator.action = action } diff --git a/TablePro/Views/Filter/SQLPreviewSheet.swift b/TablePro/Views/Filter/SQLPreviewSheet.swift index 657a57aa9..5f9868624 100644 --- a/TablePro/Views/Filter/SQLPreviewSheet.swift +++ b/TablePro/Views/Filter/SQLPreviewSheet.swift @@ -20,7 +20,7 @@ struct SQLPreviewSheet: View { VStack(spacing: 16) { HStack { Text("Generated WHERE Clause") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) Spacer() Button(action: { dismiss() }) { Image(systemName: "xmark.circle.fill") @@ -35,16 +35,16 @@ struct SQLPreviewSheet: View { ScrollView { Text(sql.isEmpty ? "(no conditions)" : sql) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .leading) .padding(12) } .frame(maxHeight: 180) .background(Color(nsColor: .textBackgroundColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium) + RoundedRectangle(cornerRadius: 6) .stroke(Color(nsColor: .separatorColor), lineWidth: 0.5) ) @@ -52,9 +52,9 @@ struct SQLPreviewSheet: View { Button(action: copyToClipboard) { HStack(spacing: 4) { Image(systemName: copied ? "checkmark" : "doc.on.doc") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) Text(copied ? "Copied!" : "Copy") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) } } .buttonStyle(.bordered) diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index b916278cf..6694c3c04 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -162,7 +162,7 @@ struct ImportDialog: View { } } .buttonStyle(.link) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) } HStack(spacing: 16) { @@ -170,7 +170,7 @@ struct ImportDialog: View { ByteCountFormatter.string(fromByteCount: fileSize, countStyle: .file), systemImage: "chart.bar.doc.horizontal" ) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) if isCountingStatements { @@ -178,12 +178,12 @@ struct ImportDialog: View { ProgressView() .controlSize(.small) Text("Counting...") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } } else if statementCount > 0 { Label("\(statementCount) statements", systemImage: "list.bullet") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } } @@ -195,7 +195,7 @@ struct ImportDialog: View { private var formatPickerView: some View { HStack(spacing: 8) { Text("Format:") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .frame(width: 80, alignment: .leading) Picker("", selection: $selectedFormatId) { @@ -213,14 +213,14 @@ struct ImportDialog: View { private var filePreviewView: some View { VStack(alignment: .leading, spacing: 6) { Text("Preview") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .semibold)) + .font(.callout.weight(.semibold)) .foregroundStyle(.primary) SQLCodePreview(text: $filePreview) .frame(height: availableFormats.count > 1 ? 220 : 280) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium) + RoundedRectangle(cornerRadius: 6) .stroke(Color(nsColor: .separatorColor), lineWidth: 1) ) } @@ -229,14 +229,14 @@ struct ImportDialog: View { private var optionsView: some View { VStack(alignment: .leading, spacing: 14) { Text("Options") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .semibold)) + .font(.callout.weight(.semibold)) .foregroundStyle(.primary) VStack(alignment: .leading, spacing: 12) { // Encoding picker (always shown, independent of plugin) HStack(spacing: 8) { Text("Encoding:") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .frame(width: 80, alignment: .leading) Picker("", selection: $selectedEncoding) { diff --git a/TablePro/Views/Import/ImportErrorView.swift b/TablePro/Views/Import/ImportErrorView.swift index f2323f449..497442ce6 100644 --- a/TablePro/Views/Import/ImportErrorView.swift +++ b/TablePro/Views/Import/ImportErrorView.swift @@ -20,29 +20,29 @@ struct ImportErrorView: View { VStack(spacing: 6) { Text("Import Failed") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) if let pluginError = error as? PluginImportError, case .statementFailed(let statement, let line, let underlyingError) = pluginError { Text("Failed at line \(line)") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) ScrollView { VStack(alignment: .leading, spacing: 8) { Text("Statement:") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium)) + .font(.callout.weight(.medium)) Text(statement) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced)) .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .leading) Text("Error:") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium)) + .font(.callout.weight(.medium)) .padding(.top, 8) Text(underlyingError.localizedDescription) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(Color(nsColor: .systemRed)) .frame(maxWidth: .infinity, alignment: .leading) } @@ -51,10 +51,10 @@ struct ImportErrorView: View { .frame(height: 150) .padding(8) .background(Color(nsColor: .textBackgroundColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) } else { Text(error?.localizedDescription ?? String(localized: "Unknown error")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } diff --git a/TablePro/Views/Import/ImportProgressView.swift b/TablePro/Views/Import/ImportProgressView.swift index 4127cb059..81edcdd17 100644 --- a/TablePro/Views/Import/ImportProgressView.swift +++ b/TablePro/Views/Import/ImportProgressView.swift @@ -14,17 +14,17 @@ struct ImportProgressView: View { var body: some View { VStack(spacing: 20) { Text("Importing...") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) VStack(spacing: 8) { HStack { if !service.state.statusMessage.isEmpty { Text(service.state.statusMessage) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) } else { Text("Executed \(service.state.processedStatements) statements") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) Spacer() } diff --git a/TablePro/Views/Import/ImportSuccessView.swift b/TablePro/Views/Import/ImportSuccessView.swift index c72d3c6bc..e020f079a 100644 --- a/TablePro/Views/Import/ImportSuccessView.swift +++ b/TablePro/Views/Import/ImportSuccessView.swift @@ -31,22 +31,22 @@ struct ImportSuccessView: View { VStack(spacing: 6) { Text(hasErrors ? "Import Completed with Errors" : "Import Successful") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) if let result { if hasErrors { Text("\(result.executedStatements) statements executed, \(result.skippedStatements) failed") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) } else { Text("\(result.executedStatements) statements executed") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) } let formattedTime = String(format: "%.2f", result.executionTime) Text(String(format: String(localized: "%@ seconds"), formattedTime)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } } @@ -79,11 +79,11 @@ struct ImportSuccessView: View { ForEach(Array(errors.enumerated()), id: \.offset) { _, error in VStack(alignment: .leading, spacing: 4) { Text("Line \(error.line): \(error.statement)") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .lineLimit(2) Text(error.errorMessage) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } } diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index a5ac7a6b4..6c09c5877 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -462,10 +462,10 @@ struct MainEditorContentView: View { .foregroundStyle(.secondary) .accessibilityHidden(true) Text("No rows returned") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) if let time = executionTime { Text(String(format: "%.3fs", time)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } Spacer() @@ -811,7 +811,7 @@ struct MainEditorContentView: View { .padding(.horizontal, 6) .padding(.vertical, 2) .background( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill(Color(nsColor: .quaternaryLabelColor)) ) Text( @@ -837,7 +837,7 @@ struct MainEditorContentView: View { .padding(.horizontal, 6) .padding(.vertical, 2) .background( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill(Color(nsColor: .quaternaryLabelColor)) ) Text("Switch Database") diff --git a/TablePro/Views/QuickSwitcher/QuickSwitcherView.swift b/TablePro/Views/QuickSwitcher/QuickSwitcherView.swift index e4288463a..aa770f1c5 100644 --- a/TablePro/Views/QuickSwitcher/QuickSwitcherView.swift +++ b/TablePro/Views/QuickSwitcher/QuickSwitcherView.swift @@ -33,7 +33,7 @@ internal struct QuickSwitcherSheet: View { VStack(spacing: 0) { // Header Text("Quick Switcher") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) .padding(.vertical, 12) Divider() @@ -118,7 +118,7 @@ internal struct QuickSwitcherSheet: View { } } header: { Text(sectionTitle(for: group.kind)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .semibold)) + .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) } } @@ -145,18 +145,18 @@ internal struct QuickSwitcherSheet: View { return HStack(spacing: 10) { Image(systemName: item.iconName) - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.default)) + .font(.system(size: 14)) .foregroundStyle(isSelected ? Color(nsColor: .alternateSelectedControlTextColor) : .secondary) Text(item.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(isSelected ? Color(nsColor: .alternateSelectedControlTextColor) : .primary) .lineLimit(1) .truncationMode(.tail) if !item.subtitle.isEmpty { Text(item.subtitle) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(isSelected ? Color(nsColor: .alternateSelectedControlTextColor).opacity(0.7) : Color.secondary) .lineLimit(1) } @@ -164,23 +164,23 @@ internal struct QuickSwitcherSheet: View { Spacer() Text(item.kindLabel) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .medium)) + .font(.caption.weight(.medium)) .foregroundStyle(isSelected ? Color(nsColor: .alternateSelectedControlTextColor).opacity(0.7) : .secondary) .padding(.horizontal, 6) .padding(.vertical, 2) .background( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill(isSelected ? Color(nsColor: .alternateSelectedControlTextColor).opacity(0.15) : Color(nsColor: .quaternaryLabelColor)) ) } .padding(.vertical, 4) .contentShape(Rectangle()) .listRowBackground( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill(isSelected ? Color(nsColor: .selectedContentBackgroundColor) : Color.clear) .padding(.horizontal, 4) ) - .listRowInsets(ThemeEngine.shared.activeTheme.spacing.listRowInsets.swiftUI) + .listRowInsets(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .listRowSeparator(.hidden) .id(item.id) .tag(item.id) @@ -199,7 +199,7 @@ internal struct QuickSwitcherSheet: View { ProgressView() .scaleEffect(0.8) Text("Loading...") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -208,18 +208,18 @@ internal struct QuickSwitcherSheet: View { private var emptyState: some View { VStack(spacing: 12) { Image(systemName: "magnifyingglass") - .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.extraLarge)) + .font(.system(size: 24)) .foregroundStyle(.secondary) if viewModel.searchText.isEmpty { Text("No objects found") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) } else { Text("No matching objects") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) Text("No objects match \"\(viewModel.searchText)\"") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } } diff --git a/TablePro/Views/Results/BooleanCellEditor.swift b/TablePro/Views/Results/BooleanCellEditor.swift index b6471d128..eef0d12f0 100644 --- a/TablePro/Views/Results/BooleanCellEditor.swift +++ b/TablePro/Views/Results/BooleanCellEditor.swift @@ -32,7 +32,7 @@ final class BooleanCellEditor: NSPopUpButton { // Style to match text fields bezelStyle = .texturedSquare - font = .monospacedSystemFont(ofSize: ThemeEngine.shared.activeTheme.typography.body, weight: .regular) + font = .monospacedSystemFont(ofSize: 13, weight: .regular) } @objc private func valueChanged() { diff --git a/TablePro/Views/Results/DataGridCellFactory.swift b/TablePro/Views/Results/DataGridCellFactory.swift index 4bd571894..fbe2cc692 100644 --- a/TablePro/Views/Results/DataGridCellFactory.swift +++ b/TablePro/Views/Results/DataGridCellFactory.swift @@ -371,7 +371,7 @@ final class DataGridCellFactory { private static let maxMeasureChars = 50 /// Font for measuring header private var headerFont: NSFont { - NSFont.systemFont(ofSize: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold) + NSFont.systemFont(ofSize: 13, weight: .semibold) } /// Calculate column width based on header name only (used for initial display) diff --git a/TablePro/Views/Results/DatePickerCellEditor.swift b/TablePro/Views/Results/DatePickerCellEditor.swift index 66c11d0a1..6e6d0a62e 100644 --- a/TablePro/Views/Results/DatePickerCellEditor.swift +++ b/TablePro/Views/Results/DatePickerCellEditor.swift @@ -71,7 +71,7 @@ final class DatePickerCellEditor: NSDatePicker { private func setupUI() { datePickerStyle = .textFieldAndStepper datePickerElements = [.yearMonthDay, .hourMinuteSecond] - font = .monospacedSystemFont(ofSize: ThemeEngine.shared.activeTheme.typography.body, weight: .regular) + font = .monospacedSystemFont(ofSize: 13, weight: .regular) isBezeled = false isBordered = false drawsBackground = false diff --git a/TablePro/Views/Results/EnumPopoverContentView.swift b/TablePro/Views/Results/EnumPopoverContentView.swift index f325715dd..cf24dbc69 100644 --- a/TablePro/Views/Results/EnumPopoverContentView.swift +++ b/TablePro/Views/Results/EnumPopoverContentView.swift @@ -37,7 +37,7 @@ struct EnumPopoverContentView: View { VStack(spacing: 0) { TextField("Search...", text: $searchText) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .padding(.horizontal, 8) .padding(.vertical, 8) @@ -70,19 +70,19 @@ struct EnumPopoverContentView: View { private func rowLabel(for value: String) -> some View { if value == enumNullMarker { Text(value) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced).italic()) + .font(.system(.callout, design: .monospaced).italic()) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.tail) } else if value == currentValue { Text(value) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .foregroundStyle(.tint) .lineLimit(1) .truncationMode(.tail) } else { Text(value) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .foregroundStyle(.primary) .lineLimit(1) .truncationMode(.tail) diff --git a/TablePro/Views/Results/ForeignKeyPopoverContentView.swift b/TablePro/Views/Results/ForeignKeyPopoverContentView.swift index d90b7fa20..90b6321f0 100644 --- a/TablePro/Views/Results/ForeignKeyPopoverContentView.swift +++ b/TablePro/Views/Results/ForeignKeyPopoverContentView.swift @@ -43,7 +43,7 @@ struct ForeignKeyPopoverContentView: View { VStack(spacing: 0) { TextField("Search...", text: $searchText) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .padding(.horizontal, 8) .padding(.vertical, 8) @@ -56,7 +56,7 @@ struct ForeignKeyPopoverContentView: View { } else if filteredValues.isEmpty { Text("No values found") .foregroundStyle(.secondary) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .frame(maxWidth: .infinity, alignment: .leading) .frame(height: 60) } else { @@ -97,13 +97,13 @@ struct ForeignKeyPopoverContentView: View { private func rowLabel(for value: FKValue) -> some View { if value.id == currentValue { Text(value.display) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .foregroundStyle(.tint) .lineLimit(1) .truncationMode(.tail) } else { Text(value.display) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .foregroundStyle(.primary) .lineLimit(1) .truncationMode(.tail) diff --git a/TablePro/Views/Results/ForeignKeyPreviewView.swift b/TablePro/Views/Results/ForeignKeyPreviewView.swift index 5e22fc5c6..d0c331d41 100644 --- a/TablePro/Views/Results/ForeignKeyPreviewView.swift +++ b/TablePro/Views/Results/ForeignKeyPreviewView.swift @@ -49,7 +49,7 @@ struct ForeignKeyPreviewView: View { private var header: some View { HStack { Text("\(fkInfo.column) → \(referencedTableDisplay).\(fkInfo.referencedColumn)") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced)) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.middle) @@ -66,7 +66,7 @@ struct ForeignKeyPreviewView: View { if cellValue == nil { Text("NULL — no referenced row") .foregroundStyle(.secondary) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .frame(maxWidth: .infinity, alignment: .center) .frame(height: 60) } else if isLoading { @@ -76,12 +76,12 @@ struct ForeignKeyPreviewView: View { } else if let errorMessage { Text(errorMessage) .foregroundStyle(Color(nsColor: .systemRed)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .padding(10) } else if values.isEmpty { Text("Referenced row not found") .foregroundStyle(.secondary) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .frame(maxWidth: .infinity, alignment: .center) .frame(height: 60) } else { @@ -91,20 +91,20 @@ struct ForeignKeyPreviewView: View { let (col, value) = pair HStack(alignment: .top, spacing: 8) { Text(col) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced)) .foregroundStyle(.secondary) .frame(width: 120, alignment: .trailing) .lineLimit(1) if let val = value { Text(val) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .foregroundStyle(.primary) .lineLimit(3) .textSelection(.enabled) } else { Text("NULL") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .foregroundStyle(.tertiary) .italic() } diff --git a/TablePro/Views/Results/HexEditorContentView.swift b/TablePro/Views/Results/HexEditorContentView.swift index af2cc5778..df4ebb2f6 100644 --- a/TablePro/Views/Results/HexEditorContentView.swift +++ b/TablePro/Views/Results/HexEditorContentView.swift @@ -169,7 +169,7 @@ private struct HexDumpDisplayView: NSViewRepresentable { textView.isEditable = false textView.isSelectable = true textView.font = NSFont.monospacedSystemFont( - ofSize: ThemeEngine.shared.activeTheme.typography.small, + ofSize: 11, weight: .regular ) textView.textContainerInset = NSSize(width: 8, height: 8) @@ -206,7 +206,7 @@ private struct HexInputTextView: NSViewRepresentable { textView.isEditable = true textView.isSelectable = true textView.font = NSFont.monospacedSystemFont( - ofSize: ThemeEngine.shared.activeTheme.typography.medium, + ofSize: 12, weight: .regular ) textView.textContainerInset = NSSize(width: 8, height: 8) diff --git a/TablePro/Views/Results/InlineErrorBanner.swift b/TablePro/Views/Results/InlineErrorBanner.swift index 0ae3a07ad..bf220d6d5 100644 --- a/TablePro/Views/Results/InlineErrorBanner.swift +++ b/TablePro/Views/Results/InlineErrorBanner.swift @@ -17,7 +17,7 @@ struct InlineErrorBanner: View { Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(Color(nsColor: .systemRed)) Text(message) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .lineLimit(3) .textSelection(.enabled) Spacer() diff --git a/TablePro/Views/Results/JSONSyntaxTextView.swift b/TablePro/Views/Results/JSONSyntaxTextView.swift index d0c0a4c6b..71c316589 100644 --- a/TablePro/Views/Results/JSONSyntaxTextView.swift +++ b/TablePro/Views/Results/JSONSyntaxTextView.swift @@ -26,7 +26,7 @@ internal struct JSONSyntaxTextView: NSViewRepresentable { textView.isEditable = isEditable textView.isSelectable = true - textView.font = NSFont.monospacedSystemFont(ofSize: ThemeEngine.shared.activeTheme.typography.medium, weight: .regular) + textView.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular) textView.textContainerInset = NSSize(width: 4, height: 4) textView.backgroundColor = .textBackgroundColor textView.textColor = NSColor.labelColor @@ -73,7 +73,7 @@ internal struct JSONSyntaxTextView: NSViewRepresentable { guard length > 0 else { return } let fullRange = NSRange(location: 0, length: length) - let font = textView.font ?? NSFont.monospacedSystemFont(ofSize: ThemeEngine.shared.activeTheme.typography.medium, weight: .regular) + let font = textView.font ?? NSFont.monospacedSystemFont(ofSize: 12, weight: .regular) let content = textStorage.string let maxHighlightLength = 10_000 let highlightRange: NSRange diff --git a/TablePro/Views/Results/ResultSuccessView.swift b/TablePro/Views/Results/ResultSuccessView.swift index 8d0bffc63..ac865b10e 100644 --- a/TablePro/Views/Results/ResultSuccessView.swift +++ b/TablePro/Views/Results/ResultSuccessView.swift @@ -20,15 +20,15 @@ struct ResultSuccessView: View { .font(.largeTitle) .foregroundStyle(Color(nsColor: .systemGreen)) Text(String(format: String(localized: "%lld row(s) affected"), Int64(rowsAffected))) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) if let time = executionTime { Text(String(format: "%.3fs", time)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } if let status = statusMessage, !status.isEmpty { Text(status) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } Spacer() diff --git a/TablePro/Views/Results/ResultTabBar.swift b/TablePro/Views/Results/ResultTabBar.swift index 9bb9ab72b..67d47382b 100644 --- a/TablePro/Views/Results/ResultTabBar.swift +++ b/TablePro/Views/Results/ResultTabBar.swift @@ -35,7 +35,7 @@ struct ResultTabBar: View { .foregroundStyle(.secondary) } Text(rs.label) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .lineLimit(1) if !rs.isPinned { Button { onClose?(rs.id) } label: { @@ -49,7 +49,7 @@ struct ResultTabBar: View { .padding(.horizontal, 10) .padding(.vertical, 6) .background( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill(isActive ? Color(nsColor: .selectedControlColor) : Color.clear) ) .contentShape(Rectangle()) diff --git a/TablePro/Views/RightSidebar/EditableFieldView.swift b/TablePro/Views/RightSidebar/EditableFieldView.swift index 5141eef82..607fab59e 100644 --- a/TablePro/Views/RightSidebar/EditableFieldView.swift +++ b/TablePro/Views/RightSidebar/EditableFieldView.swift @@ -75,13 +75,13 @@ internal struct FieldDetailView: View { } Text(context.columnName) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .lineLimit(1) Spacer() Text(context.columnType.badgeLabel) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny, weight: .medium)) + .font(.system(size: 9, weight: .medium)) .foregroundStyle(.tertiary) .padding(.horizontal, 5) .padding(.vertical, 1) @@ -90,7 +90,7 @@ internal struct FieldDetailView: View { if isTruncated && !isLoadingFullValue { Text("truncated") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny, weight: .medium)) + .font(.system(size: 9, weight: .medium)) .foregroundStyle(Color(nsColor: .systemOrange)) .padding(.horizontal, 5) .padding(.vertical, 1) diff --git a/TablePro/Views/RightSidebar/FieldEditors/BlobHexEditorView.swift b/TablePro/Views/RightSidebar/FieldEditors/BlobHexEditorView.swift index b9ab364b2..218136424 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/BlobHexEditorView.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/BlobHexEditorView.swift @@ -22,7 +22,7 @@ internal struct BlobHexEditorView: View { private var readOnlyHexView: some View { ScrollView { Text(BlobFormattingService.shared.format(context.value.wrappedValue, for: .detail) ?? "") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny, design: .monospaced)) + .font(.system(size: 9, design: .monospaced)) .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .topLeading) } @@ -33,7 +33,7 @@ internal struct BlobHexEditorView: View { VStack(alignment: .leading, spacing: 2) { TextField("Hex bytes", text: $hexEditText, axis: .vertical) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny, design: .monospaced)) + .font(.system(size: 9, design: .monospaced)) .lineLimit(3...8) .focused($isFocused) .onAppear { @@ -53,13 +53,13 @@ internal struct BlobHexEditorView: View { HStack(spacing: 4) { if let byteCount = context.value.wrappedValue.data(using: .isoLatin1)?.count, byteCount > 0 { Text("\(byteCount) bytes") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny)) + .font(.system(size: 9)) .foregroundStyle(.tertiary) } if BlobFormattingService.shared.parseHex(hexEditText) == nil, !hexEditText.isEmpty { Text("Invalid hex") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny)) + .font(.system(size: 9)) .foregroundStyle(Color(nsColor: .systemRed)) } } diff --git a/TablePro/Views/RightSidebar/FieldEditors/DropdownFieldHelper.swift b/TablePro/Views/RightSidebar/FieldEditors/DropdownFieldHelper.swift index 98e55b290..fc913c1e1 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/DropdownFieldHelper.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/DropdownFieldHelper.swift @@ -16,7 +16,7 @@ internal func dropdownField( content() } label: { Text(label) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) } diff --git a/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift b/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift index 5798afd5b..c673be811 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift @@ -59,7 +59,7 @@ internal struct FieldMenuView: View { } } label: { Image(systemName: "chevron.down") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption)) + .font(.caption) .frame(width: 20, height: 20) .contentShape(Rectangle()) } diff --git a/TablePro/Views/RightSidebar/FieldEditors/MultiLineEditorView.swift b/TablePro/Views/RightSidebar/FieldEditors/MultiLineEditorView.swift index 8778e9ab2..8c69fc132 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/MultiLineEditorView.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/MultiLineEditorView.swift @@ -13,7 +13,7 @@ internal struct MultiLineEditorView: View { var body: some View { TextField(context.placeholderText, text: context.value, axis: .vertical) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .lineLimit(3...6) .focused($isFocused) .disabled(context.isReadOnly) diff --git a/TablePro/Views/RightSidebar/FieldEditors/PendingStateOverlay.swift b/TablePro/Views/RightSidebar/FieldEditors/PendingStateOverlay.swift index 438f0576d..dea1007d9 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/PendingStateOverlay.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/PendingStateOverlay.swift @@ -17,7 +17,7 @@ internal struct PendingStateOverlay: View { if isLoadingFullValue { TextField("", text: .constant("")) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .disabled(true) .overlay { ProgressView() @@ -26,11 +26,11 @@ internal struct PendingStateOverlay: View { } else if isTruncated { TextField(String(localized: "Value excluded from query"), text: .constant("")) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .disabled(true) } else if isPendingNull || isPendingDefault { Text(isPendingNull ? "NULL" : "DEFAULT") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced)) .foregroundStyle(.secondary) .frame(maxWidth: .infinity, minHeight: minHeight, alignment: .topLeading) .padding(6) diff --git a/TablePro/Views/RightSidebar/FieldEditors/SetPickerView.swift b/TablePro/Views/RightSidebar/FieldEditors/SetPickerView.swift index 27703e75c..4f822d739 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/SetPickerView.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/SetPickerView.swift @@ -20,7 +20,7 @@ internal struct SetPickerView: View { isSetPopoverPresented = true } label: { Text(displayLabel) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) } diff --git a/TablePro/Views/RightSidebar/FieldEditors/SingleLineEditorView.swift b/TablePro/Views/RightSidebar/FieldEditors/SingleLineEditorView.swift index d36669a83..b5a6c6c6e 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/SingleLineEditorView.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/SingleLineEditorView.swift @@ -13,7 +13,7 @@ internal struct SingleLineEditorView: View { var body: some View { TextField(context.placeholderText, text: context.value) .textFieldStyle(.roundedBorder) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .focused($isFocused) .disabled(context.isReadOnly) } diff --git a/TablePro/Views/RightSidebar/RightSidebarView.swift b/TablePro/Views/RightSidebar/RightSidebarView.swift index 48d767f89..cd3635c4a 100644 --- a/TablePro/Views/RightSidebar/RightSidebarView.swift +++ b/TablePro/Views/RightSidebar/RightSidebarView.swift @@ -149,7 +149,7 @@ struct RightSidebarView: View { SearchFieldView( placeholder: "Search for field...", text: $searchText, - fontSize: ThemeEngine.shared.activeTheme.typography.small + fontSize: 11 ) .padding(.horizontal, 10) @@ -159,7 +159,7 @@ struct RightSidebarView: View { Section { if filtered.isEmpty && !searchText.isEmpty { Text("No matching fields") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.tertiary) .frame(maxWidth: .infinity) } else { diff --git a/TablePro/Views/Settings/Appearance/ThemeEditorColorsSection.swift b/TablePro/Views/Settings/Appearance/ThemeEditorColorsSection.swift index a97219a6f..5eb469019 100644 --- a/TablePro/Views/Settings/Appearance/ThemeEditorColorsSection.swift +++ b/TablePro/Views/Settings/Appearance/ThemeEditorColorsSection.swift @@ -152,33 +152,24 @@ internal struct ThemeEditorColorsSection: View { private var interfaceSection: some View { Section(String(localized: "Interface")) { - LabeledContent(String(localized: "Window Background")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.windowBackground)) - } - LabeledContent(String(localized: "Control Background")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.controlBackground)) - } - LabeledContent(String(localized: "Card Background")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.cardBackground)) - } - LabeledContent(String(localized: "Border")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.border)) - } - LabeledContent(String(localized: "Primary Text")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.primaryText)) - } - LabeledContent(String(localized: "Secondary Text")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.secondaryText)) - } - LabeledContent(String(localized: "Tertiary Text")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.tertiaryText)) - } - LabeledContent(String(localized: "Selection")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.selectionBackground)) - } - LabeledContent(String(localized: "Hover")) { - HexColorPicker(label: "", hex: colorBinding(for: \.ui.hoverBackground)) - } + optionalColorRow(String(localized: "Window Background"), keyPath: \.ui.windowBackground, + fallback: .windowBackgroundColor) + optionalColorRow(String(localized: "Control Background"), keyPath: \.ui.controlBackground, + fallback: .controlBackgroundColor) + optionalColorRow(String(localized: "Card Background"), keyPath: \.ui.cardBackground, + fallback: .controlBackgroundColor) + optionalColorRow(String(localized: "Border"), keyPath: \.ui.border, + fallback: .separatorColor) + optionalColorRow(String(localized: "Primary Text"), keyPath: \.ui.primaryText, + fallback: .labelColor) + optionalColorRow(String(localized: "Secondary Text"), keyPath: \.ui.secondaryText, + fallback: .secondaryLabelColor) + optionalColorRow(String(localized: "Tertiary Text"), keyPath: \.ui.tertiaryText, + fallback: .tertiaryLabelColor) + optionalColorRow(String(localized: "Selection"), keyPath: \.ui.selectionBackground, + fallback: .selectedContentBackgroundColor) + optionalColorRow(String(localized: "Hover"), keyPath: \.ui.hoverBackground, + fallback: .unemphasizedSelectedContentBackgroundColor) } } @@ -217,21 +208,16 @@ internal struct ThemeEditorColorsSection: View { private var sidebarSection: some View { Section(String(localized: "Sidebar")) { - LabeledContent(String(localized: "Background")) { - HexColorPicker(label: "", hex: colorBinding(for: \.sidebar.background)) - } - LabeledContent(String(localized: "Text")) { - HexColorPicker(label: "", hex: colorBinding(for: \.sidebar.text)) - } - LabeledContent(String(localized: "Selected Item")) { - HexColorPicker(label: "", hex: colorBinding(for: \.sidebar.selectedItem)) - } - LabeledContent(String(localized: "Hover")) { - HexColorPicker(label: "", hex: colorBinding(for: \.sidebar.hover)) - } - LabeledContent(String(localized: "Section Header")) { - HexColorPicker(label: "", hex: colorBinding(for: \.sidebar.sectionHeader)) - } + optionalColorRow(String(localized: "Background"), keyPath: \.sidebar.background, + fallback: .windowBackgroundColor) + optionalColorRow(String(localized: "Text"), keyPath: \.sidebar.text, + fallback: .labelColor) + optionalColorRow(String(localized: "Selected Item"), keyPath: \.sidebar.selectedItem, + fallback: .selectedContentBackgroundColor) + optionalColorRow(String(localized: "Hover"), keyPath: \.sidebar.hover, + fallback: .unemphasizedSelectedContentBackgroundColor) + optionalColorRow(String(localized: "Section Header"), keyPath: \.sidebar.sectionHeader, + fallback: .secondaryLabelColor) } } @@ -239,12 +225,10 @@ internal struct ThemeEditorColorsSection: View { private var toolbarSection: some View { Section(String(localized: "Toolbar")) { - LabeledContent(String(localized: "Secondary Text")) { - HexColorPicker(label: "", hex: colorBinding(for: \.toolbar.secondaryText)) - } - LabeledContent(String(localized: "Tertiary Text")) { - HexColorPicker(label: "", hex: colorBinding(for: \.toolbar.tertiaryText)) - } + optionalColorRow(String(localized: "Secondary Text"), keyPath: \.toolbar.secondaryText, + fallback: .secondaryLabelColor) + optionalColorRow(String(localized: "Tertiary Text"), keyPath: \.toolbar.tertiaryText, + fallback: .tertiaryLabelColor) } } @@ -265,4 +249,58 @@ internal struct ThemeEditorColorsSection: View { } ) } + + private func optionalColorBinding( + for keyPath: WritableKeyPath, + fallback: NSColor + ) -> Binding { + Binding( + get: { + if let hex = theme[keyPath: keyPath] { + return hex + } + return (fallback.usingColorSpace(.sRGB) ?? fallback).hexString + }, + set: { newValue in + guard theme.isEditable else { return } + var updated = theme + updated[keyPath: keyPath] = newValue + do { + try engine.saveUserTheme(updated) + } catch { + Self.logger.error("Failed to save theme: \(error.localizedDescription, privacy: .public)") + } + } + ) + } + + @ViewBuilder + private func optionalColorRow( + _ label: String, + keyPath: WritableKeyPath, + fallback: NSColor + ) -> some View { + LabeledContent(label) { + HStack(spacing: 4) { + HexColorPicker(label: "", hex: optionalColorBinding(for: keyPath, fallback: fallback)) + if theme[keyPath: keyPath] != nil { + Button { + guard theme.isEditable else { return } + var updated = theme + updated[keyPath: keyPath] = nil + do { + try engine.saveUserTheme(updated) + } catch { + Self.logger.error("Failed to save theme: \(error.localizedDescription, privacy: .public)") + } + } label: { + Image(systemName: "arrow.counterclockwise") + .font(.caption) + } + .buttonStyle(.borderless) + .help(String(localized: "Reset to System Default")) + } + } + } + } } diff --git a/TablePro/Views/Settings/Appearance/ThemeEditorFontsSection.swift b/TablePro/Views/Settings/Appearance/ThemeEditorFontsSection.swift index f6b4f9fda..da4bc3185 100644 --- a/TablePro/Views/Settings/Appearance/ThemeEditorFontsSection.swift +++ b/TablePro/Views/Settings/Appearance/ThemeEditorFontsSection.swift @@ -84,10 +84,10 @@ struct ThemeEditorFontsSection: View { Text("SELECT * FROM users WHERE id = 42;") .font(Font(editorFont)) .foregroundStyle(theme.editor.text.swiftUIColor) - .padding(theme.spacing.xs) + .padding(8) .frame(maxWidth: .infinity, alignment: .leading) .background(theme.editor.background.swiftUIColor) - .clipShape(RoundedRectangle(cornerRadius: theme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) } } diff --git a/TablePro/Views/Settings/Appearance/ThemeEditorLayoutSection.swift b/TablePro/Views/Settings/Appearance/ThemeEditorLayoutSection.swift deleted file mode 100644 index 47560f7dd..000000000 --- a/TablePro/Views/Settings/Appearance/ThemeEditorLayoutSection.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// ThemeEditorLayoutSection.swift -// TablePro -// - -import SwiftUI - -internal struct ThemeEditorLayoutSection: View { - private var engine: ThemeEngine { ThemeEngine.shared } - private var theme: ThemeDefinition { engine.activeTheme } - - var body: some View { - Form { - typographySection - spacingSection - iconSizesSection - cornerRadiusSection - rowHeightsSection - animationsSection - } - .formStyle(.grouped) - .scrollContentBackground(.hidden) - } - - // MARK: - Sections - - private var typographySection: some View { - Section(String(localized: "Typography")) { - numericField(String(localized: "Tiny"), keyPath: \.typography.tiny, range: 1...20) - numericField(String(localized: "Caption"), keyPath: \.typography.caption, range: 1...20) - numericField(String(localized: "Small"), keyPath: \.typography.small, range: 1...20) - numericField(String(localized: "Medium"), keyPath: \.typography.medium, range: 1...20) - numericField(String(localized: "Body"), keyPath: \.typography.body, range: 1...20) - numericField(String(localized: "Title 3"), keyPath: \.typography.title3, range: 1...30) - numericField(String(localized: "Title 2"), keyPath: \.typography.title2, range: 1...30) - } - } - - private var spacingSection: some View { - Section(String(localized: "Spacing")) { - numericField("xxxs", keyPath: \.spacing.xxxs, range: 0...10) - numericField("xxs", keyPath: \.spacing.xxs, range: 0...20) - numericField("xs", keyPath: \.spacing.xs, range: 0...30) - numericField("sm", keyPath: \.spacing.sm, range: 0...30) - numericField("md", keyPath: \.spacing.md, range: 0...40) - numericField("lg", keyPath: \.spacing.lg, range: 0...40) - numericField("xl", keyPath: \.spacing.xl, range: 0...50) - } - } - - private var iconSizesSection: some View { - Section(String(localized: "Icon Sizes")) { - numericField(String(localized: "Tiny Dot"), keyPath: \.iconSizes.tinyDot, range: 2...20) - numericField(String(localized: "Status Dot"), keyPath: \.iconSizes.statusDot, range: 2...20) - numericField(String(localized: "Small"), keyPath: \.iconSizes.small, range: 4...30) - numericField(String(localized: "Default"), keyPath: \.iconSizes.default, range: 4...30) - numericField(String(localized: "Medium"), keyPath: \.iconSizes.medium, range: 4...40) - numericField(String(localized: "Large"), keyPath: \.iconSizes.large, range: 8...50) - numericField(String(localized: "Extra Large"), keyPath: \.iconSizes.extraLarge, range: 8...60) - numericField(String(localized: "Huge"), keyPath: \.iconSizes.huge, range: 16...80) - numericField(String(localized: "Massive"), keyPath: \.iconSizes.massive, range: 32...128) - } - } - - private var cornerRadiusSection: some View { - Section(String(localized: "Corner Radius")) { - numericField(String(localized: "Small"), keyPath: \.cornerRadius.small, range: 0...20) - numericField(String(localized: "Medium"), keyPath: \.cornerRadius.medium, range: 0...20) - numericField(String(localized: "Large"), keyPath: \.cornerRadius.large, range: 0...30) - } - } - - private var rowHeightsSection: some View { - Section(String(localized: "Row Heights")) { - numericField(String(localized: "Compact"), keyPath: \.rowHeights.compact, range: 16...60) - numericField(String(localized: "Table"), keyPath: \.rowHeights.table, range: 20...80) - numericField(String(localized: "Comfortable"), keyPath: \.rowHeights.comfortable, range: 30...100) - } - } - - private var animationsSection: some View { - Section(String(localized: "Animations")) { - doubleField(String(localized: "Fast"), keyPath: \.animations.fast, range: 0.01...1.0) - doubleField(String(localized: "Normal"), keyPath: \.animations.normal, range: 0.01...1.0) - doubleField(String(localized: "Smooth"), keyPath: \.animations.smooth, range: 0.01...1.0) - doubleField(String(localized: "Slow"), keyPath: \.animations.slow, range: 0.01...2.0) - } - } - - // MARK: - Binding Helpers - - private func binding(for keyPath: WritableKeyPath) -> Binding { - Binding( - get: { theme[keyPath: keyPath] }, - set: { newValue in - guard theme.isEditable else { return } - var updated = theme - updated[keyPath: keyPath] = newValue - try? engine.saveUserTheme(updated) - } - ) - } - - private func doubleBinding(for keyPath: WritableKeyPath) -> Binding { - Binding( - get: { theme[keyPath: keyPath] }, - set: { newValue in - guard theme.isEditable else { return } - var updated = theme - updated[keyPath: keyPath] = newValue - try? engine.saveUserTheme(updated) - } - ) - } - - // MARK: - Row Helpers - - private func numericField( - _ label: String, - keyPath: WritableKeyPath, - range: ClosedRange, - step: CGFloat = 1 - ) -> some View { - LabeledContent(label) { - HStack(spacing: 4) { - TextField("", value: binding(for: keyPath), formatter: NumberFormatter()) - .frame(width: 60) - Stepper("", value: binding(for: keyPath), in: range, step: step) - .labelsHidden() - } - } - } - - private func doubleField( - _ label: String, - keyPath: WritableKeyPath, - range: ClosedRange, - step: Double = 0.05 - ) -> some View { - LabeledContent(label) { - HStack(spacing: 4) { - TextField("", value: doubleBinding(for: keyPath), format: .number.precision(.fractionLength(2))) - .frame(width: 60) - Stepper("", value: doubleBinding(for: keyPath), in: range, step: step) - .labelsHidden() - } - } - } -} diff --git a/TablePro/Views/Settings/Appearance/ThemeEditorView.swift b/TablePro/Views/Settings/Appearance/ThemeEditorView.swift index 4e379b907..c77b0a3f2 100644 --- a/TablePro/Views/Settings/Appearance/ThemeEditorView.swift +++ b/TablePro/Views/Settings/Appearance/ThemeEditorView.swift @@ -22,13 +22,11 @@ internal struct ThemeEditorView: View { private enum EditorTab: String, CaseIterable { case fonts = "Fonts" case colors = "Colors" - case layout = "Layout" var localizedName: String { switch self { case .fonts: return String(localized: "Fonts") case .colors: return String(localized: "Colors") - case .layout: return String(localized: "Layout") } } } @@ -36,7 +34,7 @@ internal struct ThemeEditorView: View { var body: some View { VStack(spacing: 0) { Text(theme.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.title3, weight: .semibold)) + .font(.title3.weight(.semibold)) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) .padding(.top, 12) @@ -77,12 +75,6 @@ internal struct ThemeEditorView: View { } else { duplicatePrompt } - case .layout: - if isEditable { - ThemeEditorLayoutSection() - } else { - duplicatePrompt - } } } @@ -97,11 +89,11 @@ internal struct ThemeEditorView: View { Text(theme.isBuiltIn ? String(localized: "This is a built-in theme.") : String(localized: "This is a registry theme.")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) .foregroundStyle(.secondary) - Text(String(localized: "Duplicate it to customize colors and layout.")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + Text(String(localized: "Duplicate it to customize colors.")) + .font(.subheadline) .foregroundStyle(.tertiary) Button(String(localized: "Duplicate Theme")) { diff --git a/TablePro/Views/Settings/Appearance/ThemeListRowView.swift b/TablePro/Views/Settings/Appearance/ThemeListRowView.swift index 41ca623ef..2fcf072ff 100644 --- a/TablePro/Views/Settings/Appearance/ThemeListRowView.swift +++ b/TablePro/Views/Settings/Appearance/ThemeListRowView.swift @@ -9,7 +9,7 @@ internal struct ThemeListRowView: View { VStack(alignment: .leading, spacing: 2) { Text(theme.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .lineLimit(1) Text(theme.isBuiltIn @@ -17,7 +17,7 @@ internal struct ThemeListRowView: View { : theme.isRegistry ? String(localized: "Registry") : String(localized: "Custom")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption)) + .font(.caption) .foregroundStyle(.secondary) } } diff --git a/TablePro/Views/Settings/AppearanceSettingsView.swift b/TablePro/Views/Settings/AppearanceSettingsView.swift index 906aaa1b3..9597aa77b 100644 --- a/TablePro/Views/Settings/AppearanceSettingsView.swift +++ b/TablePro/Views/Settings/AppearanceSettingsView.swift @@ -53,7 +53,7 @@ struct AppearanceSettingsView: View { VStack(spacing: 0) { HStack(spacing: 12) { Text("Appearance") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium)) + .font(.callout) .foregroundStyle(.secondary) Picker("", selection: $settings.appearanceMode) { diff --git a/TablePro/Views/Settings/LicenseSettingsView.swift b/TablePro/Views/Settings/LicenseSettingsView.swift index 90b8334fb..831fddac0 100644 --- a/TablePro/Views/Settings/LicenseSettingsView.swift +++ b/TablePro/Views/Settings/LicenseSettingsView.swift @@ -53,7 +53,7 @@ struct LicenseSettingsView: View { .controlSize(.small) } .padding(12) - .background(.orange.opacity(0.1), in: RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.large)) + .background(.orange.opacity(0.1), in: RoundedRectangle(cornerRadius: 8)) } Section("License") { diff --git a/TablePro/Views/Settings/ShortcutRecorderView.swift b/TablePro/Views/Settings/ShortcutRecorderView.swift index bd646a89a..ac559821c 100644 --- a/TablePro/Views/Settings/ShortcutRecorderView.swift +++ b/TablePro/Views/Settings/ShortcutRecorderView.swift @@ -144,7 +144,7 @@ final class ShortcutRecorderNSView: NSView { // Text let text = displayText let textColor: NSColor = isRecording ? .secondaryLabelColor : .labelColor - let font = NSFont.systemFont(ofSize: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium) + let font = NSFont.systemFont(ofSize: 12, weight: .medium) let attributes: [NSAttributedString.Key: Any] = [ .font: font, .foregroundColor: textColor diff --git a/TablePro/Views/Settings/SyncSettingsView.swift b/TablePro/Views/Settings/SyncSettingsView.swift index 74b63bf64..7d0b1370f 100644 --- a/TablePro/Views/Settings/SyncSettingsView.swift +++ b/TablePro/Views/Settings/SyncSettingsView.swift @@ -151,7 +151,7 @@ struct SyncSettingsView: View { .controlSize(.small) } .padding(12) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.large)) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8)) .padding() Spacer() diff --git a/TablePro/Views/Settings/ThemePreviewCard.swift b/TablePro/Views/Settings/ThemePreviewCard.swift index f65ec7395..78f8b3dc5 100644 --- a/TablePro/Views/Settings/ThemePreviewCard.swift +++ b/TablePro/Views/Settings/ThemePreviewCard.swift @@ -30,26 +30,26 @@ struct ThemePreviewCard: View { // MARK: - Standard Card private var standardCard: some View { - VStack(spacing: ThemeEngine.shared.activeTheme.spacing.xxs) { + VStack(spacing: 4) { thumbnail .frame(width: 160, height: 100) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium)) + .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.medium) + RoundedRectangle(cornerRadius: 6) .strokeBorder(isActive ? Color.accentColor : Color.clear, lineWidth: 2.5) ) .shadow(color: .black.opacity(0.1), radius: 2, y: 1) VStack(spacing: 1) { Text(theme.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .lineLimit(1) .foregroundStyle(.primary) Text(theme.isBuiltIn ? String(localized: "Built-in") : String(localized: "Custom")) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.tiny)) + .font(.system(size: 9)) .foregroundStyle(.secondary) } } @@ -63,9 +63,9 @@ struct ThemePreviewCard: View { private var compactCard: some View { thumbnail .frame(width: 72, height: 45) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .strokeBorder(isActive ? Color.accentColor : Color.clear, lineWidth: 0.5) ) } @@ -103,7 +103,8 @@ struct ThemePreviewCard: View { private var sidebarStrip: some View { ZStack(alignment: .topLeading) { Rectangle() - .fill(theme.sidebar.background.swiftUIColor) + .fill(theme.sidebar.background?.swiftUIColor + ?? Color(nsColor: .windowBackgroundColor)) VStack(alignment: .leading, spacing: size == .compact ? 3 : 4) { let widths: [CGFloat] = size == .compact @@ -112,8 +113,10 @@ struct ThemePreviewCard: View { ForEach(0..<4, id: \.self) { i in RoundedRectangle(cornerRadius: 1) .fill(i == 1 - ? theme.sidebar.selectedItem.swiftUIColor.opacity(0.6) - : theme.sidebar.text.swiftUIColor.opacity(0.25)) + ? (theme.sidebar.selectedItem?.swiftUIColor + ?? Color(nsColor: .selectedContentBackgroundColor)).opacity(0.6) + : (theme.sidebar.text?.swiftUIColor + ?? Color(nsColor: .labelColor)).opacity(0.25)) .frame( width: widths[i], height: codeLineHeight diff --git a/TablePro/Views/Sidebar/FavoriteEditDialog.swift b/TablePro/Views/Sidebar/FavoriteEditDialog.swift index 077c922a6..1e420363f 100644 --- a/TablePro/Views/Sidebar/FavoriteEditDialog.swift +++ b/TablePro/Views/Sidebar/FavoriteEditDialog.swift @@ -85,9 +85,9 @@ internal struct FavoriteEditDialog: View { .scrollContentBackground(.hidden) .padding(4) .background(Color(nsColor: .textBackgroundColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) .overlay( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .stroke(Color(nsColor: .separatorColor), lineWidth: 0.5) ) } diff --git a/TablePro/Views/Sidebar/FavoriteRowView.swift b/TablePro/Views/Sidebar/FavoriteRowView.swift index 8bb69975e..2a34f081f 100644 --- a/TablePro/Views/Sidebar/FavoriteRowView.swift +++ b/TablePro/Views/Sidebar/FavoriteRowView.swift @@ -31,7 +31,7 @@ internal struct FavoriteRowView: View { if let keyword = favorite.keyword, !keyword.isEmpty { Text(keyword) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .medium, design: .monospaced)) + .font(.system(.caption, design: .monospaced).weight(.medium)) .foregroundStyle(.secondary) .padding(.horizontal, 5) .padding(.vertical, 1) diff --git a/TablePro/Views/Sidebar/MaintenanceSheet.swift b/TablePro/Views/Sidebar/MaintenanceSheet.swift index 5dd6ed4cf..631a68fbb 100644 --- a/TablePro/Views/Sidebar/MaintenanceSheet.swift +++ b/TablePro/Views/Sidebar/MaintenanceSheet.swift @@ -53,7 +53,7 @@ struct MaintenanceSheet: View { .padding(8) .frame(maxWidth: .infinity, alignment: .leading) .background(Color(nsColor: .textBackgroundColor)) - .clipShape(RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small)) + .clipShape(RoundedRectangle(cornerRadius: 4)) } Divider() diff --git a/TablePro/Views/Sidebar/TableOperationDialog.swift b/TablePro/Views/Sidebar/TableOperationDialog.swift index 9f1b1d01b..500a80c0a 100644 --- a/TablePro/Views/Sidebar/TableOperationDialog.swift +++ b/TablePro/Views/Sidebar/TableOperationDialog.swift @@ -89,7 +89,7 @@ struct TableOperationDialog: View { VStack(spacing: 0) { // Header Text(title) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) + .font(.body.weight(.semibold)) .padding(.vertical, 16) .padding(.horizontal, 20) @@ -100,7 +100,7 @@ struct TableOperationDialog: View { // Note for multiple tables if isMultipleTables { Text("Same options will be applied to all selected tables.") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) } @@ -108,14 +108,14 @@ struct TableOperationDialog: View { VStack(alignment: .leading, spacing: 4) { Toggle(isOn: $ignoreForeignKeys) { Text("Ignore foreign key checks") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) } .toggleStyle(.checkbox) .disabled(ignoreFKDisabled) if let description = ignoreFKDescription { Text(description) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .padding(.leading, 20) } @@ -126,13 +126,13 @@ struct TableOperationDialog: View { VStack(alignment: .leading, spacing: 4) { Toggle(isOn: $cascade) { Text("Cascade") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body)) + .font(.body) } .toggleStyle(.checkbox) .disabled(cascadeDisabled) Text(cascadeDescription) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .font(.subheadline) .foregroundStyle(.secondary) .padding(.leading, 20) } diff --git a/TablePro/Views/Sidebar/TableRowView.swift b/TablePro/Views/Sidebar/TableRowView.swift index 49087490a..be37d9203 100644 --- a/TablePro/Views/Sidebar/TableRowView.swift +++ b/TablePro/Views/Sidebar/TableRowView.swift @@ -46,28 +46,28 @@ struct TableRow: View { ZStack(alignment: .bottomTrailing) { Image(systemName: table.type == .view ? "eye" : "tablecells") .foregroundStyle(TableRowLogic.iconColor(table: table, isPendingDelete: isPendingDelete, isPendingTruncate: isPendingTruncate)) - .frame(width: ThemeEngine.shared.activeTheme.iconSizes.default) + .frame(width: 14) // Pending operation indicator if isPendingDelete { Image(systemName: "minus.circle.fill") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption)) + .font(.caption) .foregroundStyle(Color(nsColor: .systemRed)) .offset(x: 4, y: 4) } else if isPendingTruncate { Image(systemName: "exclamationmark.circle.fill") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption)) + .font(.caption) .foregroundStyle(Color(nsColor: .systemOrange)) .offset(x: 4, y: 4) } } Text(table.name) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) .lineLimit(1) .foregroundStyle(TableRowLogic.textColor(isPendingDelete: isPendingDelete, isPendingTruncate: isPendingTruncate)) } - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xxs) + .padding(.vertical, 4) .accessibilityElement(children: .combine) .accessibilityLabel(TableRowLogic.accessibilityLabel(table: table, isPendingDelete: isPendingDelete, isPendingTruncate: isPendingTruncate)) } diff --git a/TablePro/Views/Structure/CreateTableView.swift b/TablePro/Views/Structure/CreateTableView.swift index 55d7d835c..72de2553e 100644 --- a/TablePro/Views/Structure/CreateTableView.swift +++ b/TablePro/Views/Structure/CreateTableView.swift @@ -93,7 +93,7 @@ struct CreateTableView: View { private var configBar: some View { HStack(spacing: 12) { Text("Table Name:") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .medium)) + .font(.body.weight(.medium)) TextField("Enter table name", text: $tableName) .textFieldStyle(.roundedBorder) diff --git a/TablePro/Views/Toolbar/ConnectionStatusView.swift b/TablePro/Views/Toolbar/ConnectionStatusView.swift index 93cc5fd22..ee4e2e766 100644 --- a/TablePro/Views/Toolbar/ConnectionStatusView.swift +++ b/TablePro/Views/Toolbar/ConnectionStatusView.swift @@ -26,7 +26,7 @@ struct ConnectionStatusView: View { // Vertical separator Divider() - .frame(height: ThemeEngine.shared.activeTheme.spacing.sm) + .frame(height: 12) // Database name (clickable to switch databases) if !databaseName.isEmpty { @@ -40,7 +40,7 @@ struct ConnectionStatusView: View { /// Database type and version info private var databaseInfoSection: some View { Text(formattedDatabaseInfo) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .regular, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced)) .foregroundStyle(ThemeEngine.shared.colors.toolbar.secondaryTextSwiftUI) .lineLimit(1) .truncationMode(.middle) @@ -77,7 +77,7 @@ struct ConnectionStatusView: View { .foregroundStyle(ThemeEngine.shared.colors.toolbar.secondaryTextSwiftUI) Text(databaseName) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.medium, weight: .medium)) + .font(.callout.weight(.medium)) .foregroundStyle(.primary) } } diff --git a/TablePro/Views/Toolbar/ConnectionSwitcherPopover.swift b/TablePro/Views/Toolbar/ConnectionSwitcherPopover.swift index 154f8c8e8..ea57967af 100644 --- a/TablePro/Views/Toolbar/ConnectionSwitcherPopover.swift +++ b/TablePro/Views/Toolbar/ConnectionSwitcherPopover.swift @@ -66,7 +66,7 @@ struct ConnectionSwitcherPopover: View { } .buttonStyle(.plain) .listRowBackground( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill( index == selectedIndex ? Color(nsColor: .selectedContentBackgroundColor) @@ -74,12 +74,12 @@ struct ConnectionSwitcherPopover: View { ) .padding(.horizontal, 4) ) - .listRowInsets(ThemeEngine.shared.activeTheme.spacing.listRowInsets.swiftUI) + .listRowInsets(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .listRowSeparator(.hidden) } } header: { Text("ACTIVE CONNECTIONS") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .semibold)) + .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) } } @@ -100,7 +100,7 @@ struct ConnectionSwitcherPopover: View { } .buttonStyle(.plain) .listRowBackground( - RoundedRectangle(cornerRadius: ThemeEngine.shared.activeTheme.cornerRadius.small) + RoundedRectangle(cornerRadius: 4) .fill( itemIndex == selectedIndex ? Color(nsColor: .selectedContentBackgroundColor) @@ -108,12 +108,12 @@ struct ConnectionSwitcherPopover: View { ) .padding(.horizontal, 4) ) - .listRowInsets(ThemeEngine.shared.activeTheme.spacing.listRowInsets.swiftUI) + .listRowInsets(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .listRowSeparator(.hidden) } } header: { Text("SAVED CONNECTIONS") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.caption, weight: .semibold)) + .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) } } diff --git a/TablePro/Views/Toolbar/ExecutionIndicatorView.swift b/TablePro/Views/Toolbar/ExecutionIndicatorView.swift index 2fe058e97..9e5c95e64 100644 --- a/TablePro/Views/Toolbar/ExecutionIndicatorView.swift +++ b/TablePro/Views/Toolbar/ExecutionIndicatorView.swift @@ -24,11 +24,11 @@ struct ExecutionIndicatorView: View { .accessibilityLabel(String(localized: "Query executing")) if let progress = clickHouseProgress { Text(progress.formattedLive) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .regular, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced).weight(.regular)) .foregroundStyle(ThemeEngine.shared.colors.toolbar.tertiaryTextSwiftUI) } else { Text("Executing...") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .regular, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced).weight(.regular)) .foregroundStyle(ThemeEngine.shared.colors.toolbar.tertiaryTextSwiftUI) } Button { @@ -42,13 +42,13 @@ struct ExecutionIndicatorView: View { .help(String(localized: "Cancel Query (⌘.)")) } else if let chProgress = lastClickHouseProgress { Text(chProgress.formattedSummary) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .regular, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced).weight(.regular)) .foregroundStyle(ThemeEngine.shared.colors.toolbar.tertiaryTextSwiftUI) .accessibilityLabel(String(format: String(localized: "Last query: %@"), chProgress.formattedSummary)) .help(String(localized: "Last query execution summary")) } else if let duration = lastDuration { Text(formattedDuration(duration)) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .regular, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced).weight(.regular)) .foregroundStyle(ThemeEngine.shared.colors.toolbar.tertiaryTextSwiftUI) .accessibilityLabel( String(format: String(localized: "Last query took %@"), formattedDuration(duration)) @@ -56,13 +56,13 @@ struct ExecutionIndicatorView: View { .help(String(localized: "Last query execution time")) } else { Text("--") - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .regular, design: .monospaced)) + .font(.system(.subheadline, design: .monospaced).weight(.regular)) .foregroundStyle(.quaternary) .accessibilityLabel(String(localized: "No query executed yet")) .help(String(localized: "Run a query to see execution time")) } } - .padding(.trailing, ThemeEngine.shared.activeTheme.spacing.xs) + .padding(.trailing, 8) } // MARK: - Helpers diff --git a/TablePro/Views/Toolbar/TagBadgeView.swift b/TablePro/Views/Toolbar/TagBadgeView.swift index 9ea3a4a1a..065a9e220 100644 --- a/TablePro/Views/Toolbar/TagBadgeView.swift +++ b/TablePro/Views/Toolbar/TagBadgeView.swift @@ -20,16 +20,16 @@ struct TagBadgeView: View { var body: some View { Text(displayName) - .font(.system(size: ThemeEngine.shared.activeTheme.typography.small, weight: .medium)) + .font(.subheadline.weight(.medium)) .foregroundStyle(tag.color.color) .lineLimit(1) // Prevent overflow from very long tag names - .padding(.horizontal, ThemeEngine.shared.activeTheme.spacing.xs) - .padding(.vertical, ThemeEngine.shared.activeTheme.spacing.xxs) + .padding(.horizontal, 8) + .padding(.vertical, 4) .background( Capsule() .fill(tag.color.color.opacity(0.2)) ) - .padding(.leading, ThemeEngine.shared.activeTheme.spacing.xs) + .padding(.leading, 8) .help(String(format: String(localized: "Tag: %@"), tag.name)) .accessibilityLabel(String(format: String(localized: "Tag: %@"), tag.name)) }