macbackend/Sources/Core/View/Alert.swift
david-swift 11c1cba681
Some checks failed
SwiftLint / SwiftLint (push) Waiting to run
Deploy Docs / publish (push) Has been cancelled
Add support for bottom actions
2025-01-31 19:14:33 +01:00

179 lines
5.4 KiB
Swift

//
// Alert.swift
// MacBackend
//
// Created by david-swift on 01.12.2024.
//
import SwiftUI
/// The alert view.
public struct Alert: SwiftUIWidget {
/// The alert's title.
var title: String
/// The alert's description.
var description: String
/// Whether the alert is presented.
var isPresented: Meta.Binding<Bool>
/// The alert's actions.
var actions: [Action] = []
/// The wrapped view.
var child: Meta.AnyView
/// An additional view in the alert, for example a text field.
var textField: (String, Meta.Binding<String>)?
/// The wrapped views.
public var wrappedViews: [String: Meta.AnyView] {
[.mainContent: child]
}
/// Initialize the alert.
/// - Parameters:
/// - title: The alert's title.
/// - description: The alert's description.
/// - isPresented: Whether the alert is presented.
/// - child: The wrapped view.
public init(title: String, description: String, isPresented: Meta.Binding<Bool>, child: Meta.AnyView) {
self.title = title
self.description = description
self.isPresented = isPresented
self.child = child
}
/// An alert action.
enum Action {
/// A regular button.
case button(button: Button)
/// A cancel button.
case cancel(button: Button)
/// A destructive button.
case destructive(button: Button)
/// Get the SwiftUI button.
@SwiftUI.ViewBuilder var button: some SwiftUI.View {
switch self {
case let .button(button):
button.button()
case let .cancel(button):
button.button(cancel: true)
case let .destructive(button):
button.button(destructive: true)
}
}
}
/// The button.
struct Button {
/// The button's label.
var label: String
/// The button's action.
var action: () -> Void
/// Whether it is the default action.
var defaultAction: Bool
/// Get the SwiftUI button.
/// - Parameters:
/// - cancel: Whether it is the cancel action.
/// - destructive: Whether it is a destructive action.
/// - Returns: The SwiftUI view.
@SwiftUI.ViewBuilder
func button(cancel: Bool = false, destructive: Bool = false) -> some SwiftUI.View {
let button = SwiftUI.Button(
label,
role: cancel ? .cancel : (destructive ? .destructive : nil),
action: action
)
if defaultAction {
button
.keyboardShortcut(.defaultAction)
} else {
button
}
}
}
/// Get the SwiftUI view.
/// - Parameter properties: The widget data.
/// - Returns: The SwiftUI view.
public static func view(properties: Self) -> some SwiftUI.View {
MacBackendView(.mainContent)
.alert(properties.title, isPresented: properties.isPresented.swiftUI) {
if let field = properties.textField {
SwiftUI.TextField(field.0, text: field.1.swiftUI)
}
SwiftUI.ForEach(Array(properties.actions.enumerated()), id: \.offset) { action in
action.element.button
}
} message: {
SwiftUI.Text(properties.description)
}
}
/// Add a button to the alert.
/// - Parameters:
/// - label: The button's label.
/// - defaultAction: Whether it is a default action.
/// - action: The handler.
/// - Returns: The alert.
public func button(
_ label: String,
default defaultAction: Bool = false,
action: @escaping () -> Void
) -> Self {
modify { alert in
alert.actions.append(.button(button: .init(label: label, action: action, defaultAction: defaultAction)))
}
}
/// Set the text field's property and present the text field.
/// - Parameters:
/// - label: The text field's label.
/// - text: The content.
/// - Returns: The alert view.
public func textField(
_ label: String,
text: Meta.Binding<String>
) -> Self {
modify { $0.textField = (label, text) }
}
/// Add a cancel button to the alert.
/// - Parameters:
/// - label: The button's label.
/// - defaultAction: Whether it is a default action.
/// - action: The handler.
/// - Returns: The alert.
public func cancelButton(
_ label: String,
default defaultAction: Bool = false,
action: @escaping () -> Void
) -> Self {
modify { alert in
alert.actions.append(.cancel(button: .init(label: label, action: action, defaultAction: defaultAction)))
}
}
/// Add a destructive button to the alert.
/// - Parameters:
/// - label: The button's label.
/// - defaultAction: Whether it is a default action.
/// - action: The handler.
/// - Returns: The alert.
public func destructiveButton(
_ label: String,
default defaultAction: Bool = false,
action: @escaping () -> Void
) -> Self {
modify { alert in
alert.actions
.append(.destructive(button: .init(label: label, action: action, defaultAction: defaultAction)))
}
}
}