diff --git a/Forji/Forji/Views/InstanceFormView.swift b/Forji/Forji/Views/InstanceFormView.swift index 1fdcebc..5d99253 100644 --- a/Forji/Forji/Views/InstanceFormView.swift +++ b/Forji/Forji/Views/InstanceFormView.swift @@ -144,19 +144,18 @@ struct InstanceFormView: View { Section { Button(action: handleSave) { - HStack { - Spacer() + ZStack { + Text(isAddMode ? "Login" : "Save") + .fontWeight(.semibold) + .opacity(isLoading ? 0 : 1) if isLoading { ProgressView() .progressViewStyle(.circular) - } else { - Text(isAddMode ? "Login" : "Save") - .fontWeight(.semibold) + .tint(.white) } - Spacer() } } - .buttonStyle(.borderedProminent) + .buttonStyle(FlatPrimaryButtonStyle()) .disabled( isLoading || serverURL.isEmpty || (authMode == .credentials @@ -330,6 +329,38 @@ struct InstanceFormView: View { } } +/// A full-width primary button style with a large hit target. +/// +/// Defines its own appearance instead of relying on `.borderedProminent`, whose built-in +/// press treatment springs/scales on release — on iOS 26 that read as the button's lower +/// edge "collapsing" when the finger lifted. This style changes only the background opacity +/// on press, with no `scaleEffect` and no spring, so the frame never moves. +private struct FlatPrimaryButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + Background(configuration: configuration) + } + + private struct Background: View { + let configuration: ButtonStyleConfiguration + @Environment(\.isEnabled) private var isEnabled + + var body: some View { + configuration.label + .font(.headline) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color.accentColor.opacity(backgroundOpacity), in: Capsule()) + .contentShape(Capsule()) + } + + private var backgroundOpacity: Double { + if !isEnabled { return 0.35 } + return configuration.isPressed ? 0.85 : 1.0 + } + } +} + #if DEBUG #Preview("Add") { InstanceFormView(authService: .previewDefault, mode: .add)