import AppKit import SwiftUI public enum PluginSettingsTheme { public enum Typography { public static var pageTitle: Font { .title2.weight(.semibold) } public static var pageDescription: Font { .subheadline } public static var sectionTitle: Font { .body.weight(.semibold) } public static var rowTitle: Font { .body.weight(.medium) } public static var emphasizedRowTitle: Font { .body.weight(.semibold) } public static var rowDescription: Font { .subheadline } public static var secondaryLabel: Font { .subheadline.weight(.medium) } public static var statusBadge: Font { .caption2.weight(.medium) } public static var rowIcon: Font { .caption.weight(.semibold) } public static var controlLabel: Font { .callout } public static var monospacedValue: Font { .system(size: 12, design: .monospaced) } } public enum Spacing { public static let pagePadding: CGFloat = 24 public static let section: CGFloat = 18 public static let sectionHeaderContent: CGFloat = 10 public static let cardContent: CGFloat = 16 public static let rowHorizontal: CGFloat = 16 public static let rowVertical: CGFloat = 10 public static let interactiveRowVertical: CGFloat = 12 public static let rowTitleDescription: CGFloat = 3 public static let rowContentControl: CGFloat = 12 public static let controlCluster: CGFloat = 8 } public enum Radius { public static let card: CGFloat = 10 public static let hostCard: CGFloat = 12 public static let control: CGFloat = 8 public static let field: CGFloat = 6 } public enum Stroke { public static let hairline: CGFloat = 0.5 public static let standard: CGFloat = 1 } public enum Size { public static let pageIcon: CGFloat = 42 public static let rowIcon: CGFloat = 18 public static let controlHeight: CGFloat = 30 public static let metricIcon: CGFloat = 36 public static let emptyStateIcon: CGFloat = 28 } public enum Palette { public static var windowBackground: Color { dynamic(light: 0xF4F5F7, dark: 0x1E1F22) } public static var sidebarBackground: Color { dynamic(light: 0xEEF0F3, dark: 0x25262A) } public static var contentBackground: Color { dynamic(light: 0xF7F8FA, dark: 0x1F2023) } public static var cardBackground: Color { dynamic(light: 0xFFFFFF, dark: 0x2A2B2F) } public static var recessedControlBackground: Color { dynamic(light: 0xF1F3F6, dark: 0x222327) } public static var fieldBackground: Color { dynamic(light: 0xFFFFFF, dark: 0x1C1D20) } public static var keycapBackground: Color { dynamic(light: 0xF8F9FB, dark: 0x2F3035) } public static var separator: Color { dynamic(light: 0xD9DDE4, dark: 0x3A3B40) } public static var cardBorder: Color { dynamic(light: 0xDDE1E7, dark: 0x3B3C42) } public static var sidebarHoverBackground: Color { Color.primary.opacity(0.05) } public static var sidebarSelectionBackground: Color { Color.accentColor.opacity(0.12) } public static var activeControlBackground: Color { Color.accentColor.opacity(0.12) } public static var recordingBackground: Color { Color.accentColor.opacity(0.08) } public static var nativeCardBackground: Color { Color(nsColor: .controlBackgroundColor) } public static var nativeFieldBackground: Color { Color(nsColor: .textBackgroundColor) } public static var nativeSeparator: Color { Color(nsColor: .separatorColor) } private static func dynamic(light lightHex: UInt32, dark darkHex: UInt32) -> Color { Color( nsColor: NSColor( name: nil, dynamicProvider: { appearance in appearance.pluginSettingsThemeIsDark ? .pluginSettingsThemeRGB(darkHex) : .pluginSettingsThemeRGB(lightHex) } ) ) } } } public enum PluginSettingsCardBackgroundStyle { case host case plugin case recessed } public enum PluginSettingsListDividerStyle { case horizontal case vertical } public struct PluginSettingsCardBackground: ViewModifier { private let style: PluginSettingsCardBackgroundStyle public init(_ style: PluginSettingsCardBackgroundStyle = .host) { self.style = style } public func body(content: Content) -> some View { content .background( RoundedRectangle(cornerRadius: radius, style: .continuous) .fill(background) ) } private var radius: CGFloat { switch style { case .host: return PluginSettingsTheme.Radius.hostCard case .plugin, .recessed: return PluginSettingsTheme.Radius.card } } private var background: Color { switch style { case .host: return PluginSettingsTheme.Palette.cardBackground case .plugin: return PluginSettingsTheme.Palette.nativeCardBackground case .recessed: return PluginSettingsTheme.Palette.recessedControlBackground } } } public struct PluginSettingsListDivider: View { private let style: PluginSettingsListDividerStyle private let leadingInset: CGFloat private let trailingInset: CGFloat public init( _ style: PluginSettingsListDividerStyle = .horizontal, leadingInset: CGFloat = PluginSettingsTheme.Spacing.rowHorizontal, trailingInset: CGFloat = PluginSettingsTheme.Spacing.rowHorizontal ) { self.style = style self.leadingInset = leadingInset self.trailingInset = trailingInset } public var body: some View { switch style { case .horizontal: Rectangle() .fill(PluginSettingsTheme.Palette.separator) .frame(height: PluginSettingsTheme.Stroke.standard) .padding(.leading, leadingInset) .padding(.trailing, trailingInset) case .vertical: Rectangle() .fill(PluginSettingsTheme.Palette.separator) .frame(width: PluginSettingsTheme.Stroke.standard) } } } public extension View { func pluginSettingsCardBackground( _ style: PluginSettingsCardBackgroundStyle = .host ) -> some View { modifier(PluginSettingsCardBackground(style)) } func pluginSettingsRowIconStyle(visualScale: CGFloat = 1) -> some View { pluginSettingsRowIconStyle( HierarchicalShapeStyle.secondary, visualScale: visualScale ) } func pluginSettingsRowIconStyle( _ foregroundStyle: S, visualScale: CGFloat = 1 ) -> some View { self .font(PluginSettingsTheme.Typography.rowIcon) .foregroundStyle(foregroundStyle) .symbolRenderingMode(.monochrome) .scaleEffect(visualScale) .frame( width: PluginSettingsTheme.Size.rowIcon, height: PluginSettingsTheme.Size.rowIcon ) } func pluginSettingsListRowPadding(interactive: Bool = false) -> some View { self .padding(.horizontal, PluginSettingsTheme.Spacing.rowHorizontal) .padding( .vertical, interactive ? PluginSettingsTheme.Spacing.interactiveRowVertical : PluginSettingsTheme.Spacing.rowVertical ) } } private extension NSAppearance { var pluginSettingsThemeIsDark: Bool { bestMatch(from: [.darkAqua, .aqua]) == .darkAqua } } private extension NSColor { static func pluginSettingsThemeRGB(_ hex: UInt32, alpha: CGFloat = 1) -> NSColor { NSColor( srgbRed: CGFloat((hex >> 16) & 0xff) / 255, green: CGFloat((hex >> 8) & 0xff) / 255, blue: CGFloat(hex & 0xff) / 255, alpha: alpha ) } }