refactor: move rule editor actions into the navigation bar

- Both editor modes commit via a checkmark confirmation button
  (role: .confirm) in the navigation bar — accessibility labels
  'Add Rule' (create) and 'Done' (edit); replaces the bottom
  'Add Rule' button, so nothing floats over the form anymore
- Edit mode gains an ellipsis 'Rule Actions' menu next to the
  checkmark holding Disable/Enable Rule and destructive Delete Rule;
  the red form rows are removed
- Tests pin the checkmark to the navigation bar with its label and
  drive disable/delete through the menu; the swipe-to-reach
  workarounds are gone. 95 passing
- Spec §6 updated
This commit is contained in:
2026-06-12 16:29:32 -04:00
parent 4ee8f01d21
commit af919c1585
4 changed files with 50 additions and 50 deletions

View File

@@ -6,9 +6,9 @@
import SwiftUI
/// The rule editor as a plain Form, always pushed inside a NavigationStack
/// (New Rule flow and detail editing both push it). Creation commits with a
/// prominent "Add Rule" button; editing uses a toolbar Done plus red
/// Disable/Delete rows.
/// (New Rule flow and detail editing both push it). Both modes commit via a
/// checkmark confirmation button in the navigation bar; edit mode adds an
/// ellipsis "Rule Actions" menu (Disable/Enable, Delete) next to it.
struct RuleEditorView: View {
enum Mode: Equatable {
case create
@@ -27,20 +27,6 @@ struct RuleEditorView: View {
Form {
nameSection
sections
if case .edit(let isEnabled) = mode {
Section {
Button(isEnabled ? "Disable Rule" : "Enable Rule") {
onToggleEnabled?()
}
.foregroundStyle(.red)
.accessibilityIdentifier("toggleEnabledButton")
Button("Delete Rule", role: .destructive) {
onDelete?()
}
.accessibilityIdentifier("deleteRuleButton")
}
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -50,32 +36,41 @@ struct RuleEditorView: View {
.lineLimit(1)
.accessibilityIdentifier("ruleEditorTitle")
}
if case .edit = mode {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
onCommit(draft.sanitized())
if case .edit(let isEnabled) = mode {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Button(isEnabled ? "Disable Rule" : "Enable Rule") {
onToggleEnabled?()
}
Button("Delete Rule", role: .destructive) {
onDelete?()
}
} label: {
Image(systemName: "ellipsis")
}
.accessibilityIdentifier("doneButton")
.accessibilityLabel("Rule Actions")
.accessibilityIdentifier("ruleActionsMenu")
}
}
}
.safeAreaInset(edge: .bottom) {
if mode == .create {
Button {
onCommit(draft.sanitized())
} label: {
Text("Add Rule")
.frame(maxWidth: .infinity)
ToolbarItem(placement: .confirmationAction) {
switch mode {
case .create:
Button(role: .confirm) {
onCommit(draft.sanitized())
} label: {
Image(systemName: "checkmark")
}
.accessibilityLabel("Add Rule")
.accessibilityIdentifier("commitRuleButton")
case .edit:
Button(role: .confirm) {
onCommit(draft.sanitized())
} label: {
Image(systemName: "checkmark")
}
.accessibilityLabel("Done")
.accessibilityIdentifier("doneButton")
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.accessibilityIdentifier("commitRuleButton")
.padding(.horizontal)
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
// Bar material so scrolling form content doesn't collide
// with the floating button.
.background(.bar)
}
}
.sheet(isPresented: $showingAppPicker) {