Tokamak icon indicating copy to clipboard operation
Tokamak copied to clipboard

Implement `Alert` in the DOM renderer

Open MaxDesiatov opened this issue 3 years ago • 5 comments

Here's the API that would be great to implement:

public struct Alert {
  public init(title: Text, message: Text? = nil, dismissButton: Alert.Button? = nil)
  public init(title: Text, message: Text? = nil, primaryButton: Alert.Button, secondaryButton: Alert.Button)
  public static func sideBySideButtons(
    title: Text,
    message: Text? = nil,
    primaryButton: Alert.Button,
    secondaryButton: Alert.Button
  ) -> Alert

  public struct Button {
    public static func `default`(_ label: Text, action: (() -> Void)? = {}) -> Alert.Button
    public static func cancel(_ label: Text, action: (() -> Void)? = {}) -> Alert.Button
    public static func cancel(_ action: (() -> Void)? = {}) -> Alert.Button
    public static func destructive(_ label: Text, action: (() -> Void)? = {}) -> Alert.Button
  }
}

extension View {
  public func alert<Item>(
    item: Binding<Item?>,
    content: (Item) -> Alert
  ) -> some View where Item : Identifiable
  
  public func alert(isPresented: Binding<Bool>, content: () -> Alert) -> some View
}

MaxDesiatov avatar Dec 04 '20 17:12 MaxDesiatov

@MaxDesiatov I'm interested in how SwiftUI-like alerts can be implemented in a web browser context and would love to know your general thought on how would this problem be approached.

Currently I'm aware of the ability to use JavaScriptKit to present an alert using the alert() method like this:

_ = JSObject.global.alert!("The message to be shown to the user.")

Although this alert contains a default browser provided style and dismiss button, the ability to provide custom actions is not possible.

There's also the confirm() method which displays an OK and Cancel buttons and returns a boolean depending on which value is selected, but it also is not customizable in terms of the actions:

let ok = JSObject.global.confirm!("Are you sure?").boolean!

And the prompt() method which displays an input field and returns its value or null.

let text = JSObject.global.prompt!("How old are you?").string

I think there are two approaches here:

  1. Try to fit the above three methods in an API that resembles the SwiftUI signatures as much as possible while also respecting the limits of the web browser dialogs.
  2. Implement a completely custom UI that fully resembles the SwiftUI signatures and replaces the web browser provided dialogs.

I'm leaning into option one but it may result in an API slightly different than SwiftUI's. For example, it would not require the caller to provide custom buttons for the actions. I'm also curious if there's any other way this can be implemented.

alobaili avatar Jan 29 '22 17:01 alobaili

There's also the <dialog> element, but it has poor browser support atm.

carson-katri avatar Jan 29 '22 18:01 carson-katri

There's also the <dialog> element, but it has poor browser support atm.

This seems to be the best match and is more flexible to customize.

Thank you for letting me know about it!

I agree that it seems still under development in Safari and Firefox, but I tested the polyfill implementation linked in the documentation and it seems to work. I'm not sure if it would be OK or even possible to use it in Tokamak.

alobaili avatar Jan 30 '22 17:01 alobaili

According to the latest documentation, the Alert structure is deprecated, and replaced with new presentation modifiers on View:

extension View {
    public func alert<S, A>(_ title: S, isPresented: Binding<Bool>, @ViewBuilder actions: () -> A) -> some View where S : StringProtocol, A : View
    public func alert<A>(_ title: Text, isPresented: Binding<Bool>, @ViewBuilder actions: () -> A) -> some View where A : View
    public func alert<A>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, @ViewBuilder actions: () -> A) -> some View where A : View
    public func alert<A, T>(_ title: Text, isPresented: Binding<Bool>, presenting data: T?, @ViewBuilder actions: (T) -> A) -> some View where A : View
    public func alert<A, T>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, presenting data: T?, @ViewBuilder actions: (T) -> A) -> some View where A : View
    public func alert<S, A, T>(_ title: S, isPresented: Binding<Bool>, presenting data: T?, @ViewBuilder actions: (T) -> A) -> some View where S : StringProtocol, A : View
    public func alert<E, A>(isPresented: Binding<Bool>, error: E?, @ViewBuilder actions: () -> A) -> some View where E : LocalizedError, A : View

    public func alert<S, A, M>(_ title: S, isPresented: Binding<Bool>, @ViewBuilder actions: () -> A, @ViewBuilder message: () -> M) -> some View where S : StringProtocol, A : View, M : View
    public func alert<A, M>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, @ViewBuilder actions: () -> A, @ViewBuilder message: () -> M) -> some View where A : View, M : View
    public func alert<A, M>(_ title: Text, isPresented: Binding<Bool>, @ViewBuilder actions: () -> A, @ViewBuilder message: () -> M) -> some View where A : View, M : View
    public func alert<A, M, T>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, presenting data: T?, @ViewBuilder actions: (T) -> A, @ViewBuilder message: (T) -> M) -> some View where A : View, M : View
    public func alert<A, M, T>(_ title: Text, isPresented: Binding<Bool>, presenting data: T?, @ViewBuilder actions: (T) -> A, @ViewBuilder message: (T) -> M) -> some View where A : View, M : View
    public func alert<S, A, M, T>(_ title: S, isPresented: Binding<Bool>, presenting data: T?, @ViewBuilder actions: (T) -> A, @ViewBuilder message: (T) -> M) -> some View where S : StringProtocol, A : View, M : View
    public func alert<E, A, M>(isPresented: Binding<Bool>, error: E?, @ViewBuilder actions: (E) -> A, @ViewBuilder message: (E) -> M) -> some View where E : LocalizedError, A : View, M : View
}

Documentation for them can be found here: https://developer.apple.com/documentation/swiftui/view-presentation#topics

alobaili avatar Feb 07 '22 14:02 alobaili

Anyone interested in being sponsored to add dialog alerts/sheets?

aehlke avatar Sep 19 '23 16:09 aehlke