diff --git a/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift b/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift index d9cd7f6..6e6cf5a 100644 --- a/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift +++ b/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift @@ -231,7 +231,8 @@ private struct SidebarRouteRow: View { Text(route.subtitle) .font(AtlasTypography.captionSmall) .foregroundStyle(.secondary) - .lineLimit(2) + .lineLimit(1) + .truncationMode(.tail) } } icon: { ZStack { diff --git a/Apps/AtlasApp/Sources/AtlasApp/AtlasApp.swift b/Apps/AtlasApp/Sources/AtlasApp/AtlasApp.swift index 2454dc9..665ed87 100644 --- a/Apps/AtlasApp/Sources/AtlasApp/AtlasApp.swift +++ b/Apps/AtlasApp/Sources/AtlasApp/AtlasApp.swift @@ -9,7 +9,7 @@ struct AtlasApp: App { WindowGroup(AtlasL10n.string("app.name")) { AppShellView(model: model) .environment(\.locale, model.appLanguage.locale) - .frame(minWidth: 1120, minHeight: 720) + .frame(minWidth: 940, minHeight: 640) } .commands { AtlasAppCommands(model: model) diff --git a/Apps/AtlasAppUITests/AtlasAppUITests.swift b/Apps/AtlasAppUITests/AtlasAppUITests.swift index 2baea66..0e229ef 100644 --- a/Apps/AtlasAppUITests/AtlasAppUITests.swift +++ b/Apps/AtlasAppUITests/AtlasAppUITests.swift @@ -80,6 +80,7 @@ final class AtlasAppUITests: XCTestCase { let app = XCUIApplication() let stateFile = NSTemporaryDirectory() + UUID().uuidString + "/workspace-state.json" app.launchEnvironment["ATLAS_STATE_FILE"] = stateFile + app.launchArguments += ["-ApplePersistenceIgnoreState", "YES"] return app } } diff --git a/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasBrand.swift b/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasBrand.swift index 609264e..1b515a2 100644 --- a/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasBrand.swift +++ b/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasBrand.swift @@ -135,7 +135,7 @@ public enum AtlasSpacing { /// 24pt — screen-level vertical rhythm. public static let xxl: CGFloat = 24 /// 28pt — screen horizontal margin. - public static let screenH: CGFloat = 28 + public static let screenH: CGFloat = 24 /// 32pt — large section separation. public static let section: CGFloat = 32 } @@ -227,21 +227,21 @@ public enum AtlasMotion { /// Shared layout constants. public enum AtlasLayout { /// Maximum content reading width — prevents overly long text lines. - public static let maxReadingWidth: CGFloat = 960 + public static let maxReadingWidth: CGFloat = 920 /// Standard 3-column metric grid definition. public static let metricColumns: [GridItem] = [ - GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg), - GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg), - GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg), + GridItem(.flexible(minimum: 180), spacing: AtlasSpacing.lg), + GridItem(.flexible(minimum: 180), spacing: AtlasSpacing.lg), + GridItem(.flexible(minimum: 180), spacing: AtlasSpacing.lg), ] /// 2-column grid for wider cards. public static let wideColumns: [GridItem] = [ - GridItem(.flexible(minimum: 300), spacing: AtlasSpacing.lg), - GridItem(.flexible(minimum: 300), spacing: AtlasSpacing.lg), + GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg), + GridItem(.flexible(minimum: 220), spacing: AtlasSpacing.lg), ] /// Sidebar width range. - public static let sidebarMinWidth: CGFloat = 230 - public static let sidebarIdealWidth: CGFloat = 260 + public static let sidebarMinWidth: CGFloat = 180 + public static let sidebarIdealWidth: CGFloat = 220 /// Sidebar icon container size (pill-style like System Settings). public static let sidebarIconSize: CGFloat = 32 } diff --git a/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasDesignSystem.swift b/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasDesignSystem.swift index 69f7213..0bb528d 100644 --- a/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasDesignSystem.swift +++ b/Packages/AtlasDesignSystem/Sources/AtlasDesignSystem/AtlasDesignSystem.swift @@ -76,37 +76,52 @@ public struct AtlasScreen: View { } public var body: some View { - ZStack { - LinearGradient( - colors: [AtlasColor.canvasTop, AtlasColor.canvasBottom], - startPoint: .top, - endPoint: .bottom - ) - .ignoresSafeArea() + GeometryReader { proxy in + let horizontalPadding = resolvedHorizontalPadding(for: proxy.size.width) - Group { - if useScrollView { - ScrollView { - contentStack + ZStack { + LinearGradient( + colors: [AtlasColor.canvasTop, AtlasColor.canvasBottom], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() + + Group { + if useScrollView { + ScrollView { + contentStack(horizontalPadding: horizontalPadding) + } + } else { + contentStack(horizontalPadding: horizontalPadding) } - } else { - contentStack } } } } - private var contentStack: some View { + private func contentStack(horizontalPadding: CGFloat) -> some View { VStack(alignment: .leading, spacing: AtlasSpacing.xxl) { header content } .frame(maxWidth: AtlasLayout.maxReadingWidth, maxHeight: .infinity, alignment: .topLeading) - .padding(.horizontal, AtlasSpacing.screenH) + .padding(.horizontal, horizontalPadding) .padding(.vertical, AtlasSpacing.xxl) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } + private func resolvedHorizontalPadding(for width: CGFloat) -> CGFloat { + switch width { + case ..<820: + return 16 + case ..<980: + return 20 + default: + return AtlasSpacing.screenH + } + } + private var header: some View { VStack(alignment: .leading, spacing: AtlasSpacing.sm) { Text(title) diff --git a/Packages/AtlasFeaturesApps/Sources/AtlasFeaturesApps/AppsFeatureView.swift b/Packages/AtlasFeaturesApps/Sources/AtlasFeaturesApps/AppsFeatureView.swift index 0db23d7..22d1f3a 100644 --- a/Packages/AtlasFeaturesApps/Sources/AtlasFeaturesApps/AppsFeatureView.swift +++ b/Packages/AtlasFeaturesApps/Sources/AtlasFeaturesApps/AppsFeatureView.swift @@ -105,8 +105,8 @@ public struct AppsFeatureView: View { tone: selectedAppMatchingPreview == nil ? .neutral : .warning ) { GeometryReader { proxy in - let isWide = proxy.size.width >= 760 - let sidebarWidth = min(max(proxy.size.width * 0.32, 260), 300) + let isWide = proxy.size.width >= 680 + let sidebarWidth = min(max(proxy.size.width * 0.3, 220), 280) Group { if isWide { @@ -455,36 +455,50 @@ private struct AppDetailView: View { } } - HStack(alignment: .center, spacing: AtlasSpacing.md) { - Group { - if previewPlan == nil { - Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) { - onPreview() - } - .buttonStyle(.atlasPrimary) - } else { - Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) { - onPreview() - } - .buttonStyle(.atlasSecondary) - } + ViewThatFits(in: .horizontal) { + HStack(alignment: .center, spacing: AtlasSpacing.md) { + previewButton + uninstallButton } - .disabled(isBusy) - .accessibilityIdentifier("apps.preview.\(app.id.uuidString)") - .accessibilityHint(AtlasL10n.string("apps.preview.hint")) - Button(isUninstalling ? AtlasL10n.string("apps.uninstall.running") : AtlasL10n.string("apps.uninstall.action")) { - onUninstall() + VStack(alignment: .leading, spacing: AtlasSpacing.md) { + previewButton + uninstallButton } - .buttonStyle(.atlasPrimary) - .disabled(isBusy || previewPlan == nil) - .accessibilityIdentifier("apps.uninstall.\(app.id.uuidString)") - .accessibilityHint(AtlasL10n.string("apps.uninstall.hint")) } } .frame(maxWidth: .infinity, alignment: .leading) } + private var previewButton: some View { + Group { + if previewPlan == nil { + Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) { + onPreview() + } + .buttonStyle(.atlasPrimary) + } else { + Button(isBuildingPreview ? AtlasL10n.string("apps.preview.running") : AtlasL10n.string("apps.preview.action")) { + onPreview() + } + .buttonStyle(.atlasSecondary) + } + } + .disabled(isBusy) + .accessibilityIdentifier("apps.preview.\(app.id.uuidString)") + .accessibilityHint(AtlasL10n.string("apps.preview.hint")) + } + + private var uninstallButton: some View { + Button(isUninstalling ? AtlasL10n.string("apps.uninstall.running") : AtlasL10n.string("apps.uninstall.action")) { + onUninstall() + } + .buttonStyle(.atlasPrimary) + .disabled(isBusy || previewPlan == nil) + .accessibilityIdentifier("apps.uninstall.\(app.id.uuidString)") + .accessibilityHint(AtlasL10n.string("apps.uninstall.hint")) + } + private func icon(for kind: ActionItem.Kind) -> String { switch kind { case .removeCache: diff --git a/Packages/AtlasFeaturesHistory/Sources/AtlasFeaturesHistory/HistoryFeatureView.swift b/Packages/AtlasFeaturesHistory/Sources/AtlasFeaturesHistory/HistoryFeatureView.swift index 58bff2c..ac80062 100644 --- a/Packages/AtlasFeaturesHistory/Sources/AtlasFeaturesHistory/HistoryFeatureView.swift +++ b/Packages/AtlasFeaturesHistory/Sources/AtlasFeaturesHistory/HistoryFeatureView.swift @@ -96,8 +96,8 @@ public struct HistoryFeatureView: View { } GeometryReader { proxy in - let isWide = proxy.size.width >= 760 - let sidebarWidth = min(max(proxy.size.width * 0.32, 250), 290) + let isWide = proxy.size.width >= 680 + let sidebarWidth = min(max(proxy.size.width * 0.3, 210), 270) Group { if isWide { diff --git a/Packages/AtlasFeaturesPermissions/Sources/AtlasFeaturesPermissions/PermissionsFeatureView.swift b/Packages/AtlasFeaturesPermissions/Sources/AtlasFeaturesPermissions/PermissionsFeatureView.swift index 9c87c42..a1fa809 100644 --- a/Packages/AtlasFeaturesPermissions/Sources/AtlasFeaturesPermissions/PermissionsFeatureView.swift +++ b/Packages/AtlasFeaturesPermissions/Sources/AtlasFeaturesPermissions/PermissionsFeatureView.swift @@ -82,20 +82,15 @@ public struct PermissionsFeatureView: View { ) } - HStack(alignment: .center, spacing: AtlasSpacing.md) { - if let nextActionKind { - Button(buttonTitle(for: nextActionKind)) { - performAction(for: nextActionKind) - } - .buttonStyle(.atlasPrimary) + ViewThatFits(in: .horizontal) { + HStack(alignment: .center, spacing: AtlasSpacing.md) { + nextStepButtons + Spacer(minLength: 0) } - Button(action: onRefresh) { - Label(AtlasL10n.string("permissions.refresh"), systemImage: "arrow.clockwise") + VStack(alignment: .leading, spacing: AtlasSpacing.md) { + nextStepButtons } - .buttonStyle(.atlasSecondary) - .accessibilityIdentifier("permissions.refresh") - .accessibilityHint(AtlasL10n.string("permissions.refresh.hint")) } } } @@ -224,6 +219,23 @@ public struct PermissionsFeatureView: View { return nextActionKind.systemImage } + @ViewBuilder + private var nextStepButtons: some View { + if let nextActionKind { + Button(buttonTitle(for: nextActionKind)) { + performAction(for: nextActionKind) + } + .buttonStyle(.atlasPrimary) + } + + Button(action: onRefresh) { + Label(AtlasL10n.string("permissions.refresh"), systemImage: "arrow.clockwise") + } + .buttonStyle(.atlasSecondary) + .accessibilityIdentifier("permissions.refresh") + .accessibilityHint(AtlasL10n.string("permissions.refresh.hint")) + } + @ViewBuilder private func permissionRow(_ state: PermissionState) -> some View { AtlasDetailRow( diff --git a/Packages/AtlasFeaturesSettings/Sources/AtlasFeaturesSettings/SettingsFeatureView.swift b/Packages/AtlasFeaturesSettings/Sources/AtlasFeaturesSettings/SettingsFeatureView.swift index e330ed4..bb8a6cb 100644 --- a/Packages/AtlasFeaturesSettings/Sources/AtlasFeaturesSettings/SettingsFeatureView.swift +++ b/Packages/AtlasFeaturesSettings/Sources/AtlasFeaturesSettings/SettingsFeatureView.swift @@ -40,22 +40,24 @@ public struct SettingsFeatureView: View { subtitle: AtlasL10n.string("settings.panel.subtitle") ) { VStack(alignment: .leading, spacing: AtlasSpacing.xl) { - HStack(spacing: AtlasSpacing.sm) { - ForEach(SettingsPanel.allCases) { panel in - Group { - if selectedPanel == panel { - Button(panel.title) { - selectedPanel = panel + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: AtlasSpacing.sm) { + ForEach(SettingsPanel.allCases) { panel in + Group { + if selectedPanel == panel { + Button(panel.title) { + selectedPanel = panel + } + .buttonStyle(.atlasSecondary) + } else { + Button(panel.title) { + selectedPanel = panel + } + .buttonStyle(.atlasGhost) } - .buttonStyle(.atlasSecondary) - } else { - Button(panel.title) { - selectedPanel = panel - } - .buttonStyle(.atlasGhost) } + .accessibilityIdentifier("settings.panel.\(panel.id)") } - .accessibilityIdentifier("settings.panel.\(panel.id)") } } @@ -237,21 +239,32 @@ public struct SettingsFeatureView: View { .font(AtlasTypography.body) .foregroundStyle(.secondary) - HStack(alignment: .center, spacing: AtlasSpacing.md) { - Button(AtlasL10n.string("settings.trust.documents.ack")) { - presentedDocument = .acknowledgement(settings.acknowledgementText) + ViewThatFits(in: .horizontal) { + HStack(alignment: .center, spacing: AtlasSpacing.md) { + trustDocumentButtons } - .buttonStyle(.atlasSecondary) - Button(AtlasL10n.string("settings.trust.documents.notices")) { - presentedDocument = .notices(settings.thirdPartyNoticesText) + VStack(alignment: .leading, spacing: AtlasSpacing.md) { + trustDocumentButtons } - .buttonStyle(.atlasSecondary) } } } .frame(maxWidth: .infinity, alignment: .leading) } + + @ViewBuilder + private var trustDocumentButtons: some View { + Button(AtlasL10n.string("settings.trust.documents.ack")) { + presentedDocument = .acknowledgement(settings.acknowledgementText) + } + .buttonStyle(.atlasSecondary) + + Button(AtlasL10n.string("settings.trust.documents.notices")) { + presentedDocument = .notices(settings.thirdPartyNoticesText) + } + .buttonStyle(.atlasSecondary) + } } private enum SettingsPanel: String, CaseIterable, Identifiable { diff --git a/Packages/AtlasFeaturesSmartClean/Sources/AtlasFeaturesSmartClean/SmartCleanFeatureView.swift b/Packages/AtlasFeaturesSmartClean/Sources/AtlasFeaturesSmartClean/SmartCleanFeatureView.swift index 0747869..58beb13 100644 --- a/Packages/AtlasFeaturesSmartClean/Sources/AtlasFeaturesSmartClean/SmartCleanFeatureView.swift +++ b/Packages/AtlasFeaturesSmartClean/Sources/AtlasFeaturesSmartClean/SmartCleanFeatureView.swift @@ -86,38 +86,20 @@ public struct SmartCleanFeatureView: View { systemImage: primaryAction.systemImage ) - HStack(alignment: .center, spacing: AtlasSpacing.md) { - Button(action: primaryAction.handler(startScan: onStartScan, refreshPreview: onRefreshPreview, executePlan: onExecutePlan)) { - Label(primaryAction.buttonTitle, systemImage: primaryAction.buttonSystemImage) + ViewThatFits(in: .horizontal) { + HStack(alignment: .center, spacing: AtlasSpacing.md) { + primaryActionButton + supportingActionButtons + Spacer(minLength: 0) } - .buttonStyle(.atlasPrimary) - .keyboardShortcut(.defaultAction) - .disabled(primaryAction.isDisabled(canExecutePlan: canExecutePlan)) - .accessibilityIdentifier(primaryAction.accessibilityIdentifier) - .accessibilityHint(primaryAction.accessibilityHint) - if primaryAction != .scan { - Button(action: onStartScan) { - Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles") + VStack(alignment: .leading, spacing: AtlasSpacing.md) { + primaryActionButton + HStack(alignment: .center, spacing: AtlasSpacing.md) { + supportingActionButtons + Spacer(minLength: 0) } - .buttonStyle(.atlasSecondary) - .keyboardShortcut("s", modifiers: [.command, .option]) - .disabled(isScanning || isExecutingPlan) - .accessibilityIdentifier("smartclean.runScan") - .accessibilityHint(AtlasL10n.string("smartclean.action.runScan.hint")) } - - if primaryAction != .refresh, !findings.isEmpty { - Button(action: onRefreshPreview) { - Label(AtlasL10n.string("smartclean.action.refreshPreview"), systemImage: "arrow.clockwise") - } - .buttonStyle(.atlasGhost) - .disabled(isScanning || isExecutingPlan) - .accessibilityIdentifier("smartclean.refreshPreview") - .accessibilityHint(AtlasL10n.string("smartclean.action.refreshPreview.hint")) - } - - Spacer(minLength: 0) } } } @@ -399,6 +381,41 @@ public struct SmartCleanFeatureView: View { return .refresh } + private var primaryActionButton: some View { + Button(action: primaryAction.handler(startScan: onStartScan, refreshPreview: onRefreshPreview, executePlan: onExecutePlan)) { + Label(primaryAction.buttonTitle, systemImage: primaryAction.buttonSystemImage) + } + .buttonStyle(.atlasPrimary) + .keyboardShortcut(.defaultAction) + .disabled(primaryAction.isDisabled(canExecutePlan: canExecutePlan)) + .accessibilityIdentifier(primaryAction.accessibilityIdentifier) + .accessibilityHint(primaryAction.accessibilityHint) + } + + @ViewBuilder + private var supportingActionButtons: some View { + if primaryAction != .scan { + Button(action: onStartScan) { + Label(AtlasL10n.string("smartclean.action.runScan"), systemImage: "sparkles") + } + .buttonStyle(.atlasSecondary) + .keyboardShortcut("s", modifiers: [.command, .option]) + .disabled(isScanning || isExecutingPlan) + .accessibilityIdentifier("smartclean.runScan") + .accessibilityHint(AtlasL10n.string("smartclean.action.runScan.hint")) + } + + if primaryAction != .refresh, !findings.isEmpty { + Button(action: onRefreshPreview) { + Label(AtlasL10n.string("smartclean.action.refreshPreview"), systemImage: "arrow.clockwise") + } + .buttonStyle(.atlasGhost) + .disabled(isScanning || isExecutingPlan) + .accessibilityIdentifier("smartclean.refreshPreview") + .accessibilityHint(AtlasL10n.string("smartclean.action.refreshPreview.hint")) + } + } + private func supportText(for kind: ActionItem.Kind) -> String { switch kind { case .removeCache: