#leptos #popup #modal #web-framework #events #swal #sweetalert

leptos_sweetalert

An unofficial remake of SweetAlert for the Leptos web framework

3 stable releases

1.0.2 Apr 13, 2024

#3 in #modal

MIT license

280KB
673 lines

SweetAlert for Leptos

SweetAlert is a JavaScript library for creating nice popups on the web.

My repo is a crate that allows you to use something similar in the Leptos web framework.

NOTE: This is not official. The SweetAlert team did not create this repo. I just wanted to be able to use SweetAlert in Leptos therefore I tried to re-create it myself. IT IS NOT EXACTLY THE SAME THING. I did not simply copy the CSS, I wrote it myself since my goal is not to have a perfect replica of SweetAlert, but to simplify the work for people that would like to have something similar. It is very easy to modify yourself, and if you want to customize it then feel free to simply copy the source files and the CSS files into your project.

How to use

First, you'd need to initialize the event handlers that allow the users to dismiss the Swal using the Escape key. It is also responsible for restricting the focus within the popup.

use leptos_sweetalert::*;

fn main() {
    Swal::init_key_handlers();
    // your leptos main function's body...
}

Then, use the Swal module and its fire() method to display a popup:

#[component]
fn App() -> impl IntoView {
    let confirm = move |_| {
        Swal::fire(SwalOptions {
            title: "This is a title",
            text: "This is some text",
            icon: SwalIcon::SUCCESS,
            confirm_button_text: "LETS GO",
            show_cancel_button: true,
            show_deny_button: true,
            ..SwalOptions::default()
        });
    };

    view! {
        <button on:click=confirm>Click to confirm</button>
    }
}

As you can see in the example above, the options you give to the fire() method is a struct named SwalOptions. As you probably already know, all the fields of a struct must be given a value when initialized. However, there is a lot of fields and it's quite inconvenient. Therefore, SwalOptions implements the Default trait whose purpose is to give a default value to all fields. Use the .. syntax to let Rust assign the default values to all fields that you don't specify. The default values are the same as those used by SweetAlert.

The example above displays a popup that looks like this:

Demo

NOTE: there may have been some changes in the styles of the popup since this demo was first made.

For more info on how to use this crate, please see the examples and the Rust documentation on crates.io.

Styles

Of course, without any styles the popup won't look good. Default styles and themes are provided for you in styles. Use the stylesheets in your index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">

      <!-- The core styles are necessary: -->
      <link data-trunk rel="scss" rel="stylesheet" href="../styles/sweetalert-core.scss" />
      <!-- This is an optional theme. By default you don't need this: -->
      <link data-trunk rel="scss" rel="stylesheet" href="../styles/sweetalert-theme-borderless.scss" />

      <title>SweetAlert for Leptos demo</title>
  </head>
  <body>
  </body>
</html>

Generic types of SwalOptions

It's important to note that in order to make the use of the popup easier for the developer the SwalOptions struct is generic:

#[derive(Debug, Clone)]
pub struct SwalOptions<S = &'static str, I = SwalIcon>
where
    S: AsRef<str> + Clone + Default + leptos::IntoView,
    I: SwalIconLike + Default + Clone + Copy,
{
    // ...
}

Meaning that the Swal can be opened like this:

let some_title = String::from("This is a title");
Swal::fire(SwalOptions {
    // Choose between String or &str for the type of the text fields.
    // However you can't combine both in the same struct.
    title: some_title,
    icon: SwalIcon::SUCCESS,
    ..SwalOptions::default()
});

In the example above both generic types are deduced (the string-like type, S, and I which is the type used for the icon). By default, the type of S is a &'static str and the type of I is SwalIcon. Unfortunately, in Rust it isn't possible to use a struct with default generic parameters without explicitly specifying one of them. As a consequence, if you're displaying a popup without an icon, then this syntax will be mandatory:

// Here "&str" is the type used for the text fields.
// Use whatever type suits you as long as it respects the constraints defined above.
Swal::fire(SwalOptions::<&str> {
    title: "Hello",
    ..SwalOptions::default()
});

Open multiple popups in a row

You can call a function when the user presses a button in the popup, and this function can very well open a new popup:

#[component]
fn App() -> impl IntoView {
    let confirm = move |_| {
        Swal::fire(SwalOptions {
            title: "This is a title",
            text: "This is some text",
            icon: SwalIcon::SUCCESS,
            // pre_confirm gets executed when the user clicks on the Confirm button.
            // It's prefixed with "pre" because it gets executed before the
            // popup closes, not after (see the 'then' field in the doc on crates.io).
            pre_confirm: || {
                Swal::fire(SwalOptions::<&str> {
                    title: "Confirmed !",
                    ..SwalOptions::default()
                });
            },
            ..SwalOptions::default()
        });
    };

    view! {
        <button on:click=confirm>Click to confirm</button>
    }
}

You can close a popup programmatically via the Swal::close() method. Under the hood the Swal::fire() method closes the popup, waits for the closing animation to finish and then opens a new one with the given parameters. Therefore, if you're planning on opening a new popup whereas there is already one opened, then...

don't do this:

// This would unsync the animations,
// leading to weird behavior:
Swal::close(None);
Swal::fire(SwalOptions {
    title: "Hello",
    icon: SwalIcon::SUCCESS,
    ..SwalOptions::default()
});

What about asynchronous actions?

This crate does not support async/await, meaning that if you want to realize an asynchronous action via the popup, then you have to control the popup's lifetime on your own. Here is an example:

#[component]
fn App() -> impl IntoView {
    let confirm = move |_| {
        Swal::fire(SwalOptions {
            // By default, the Swal closes itself as soon as one of the buttons is pressed,
            // or if the user has either clicked on the backdrop or pressed the Escape key.
            // However, you can tell the popup to persist:
            auto_close: false,

            title: "This is a title",
            text: "This is some text",
            icon: SwalIcon::SUCCESS,
            confirm_button_text: "LETS GO",
            show_cancel_button: true,
            show_deny_button: true,
            pre_confirm: || {
                // Perform your asynchronous actions here...
                // ...
                //
                // Once you're done, you can manually close the popup.
                // You have to give a reason explaining why this popup is closing.
                // The result will then be passed to the `then` callback.
                // If you don't care about the result then just use `None`.
                //
                // Closing a popup without mentioning a result will not trigger the "then" callback.
                Swal::close(Some(SwalResult::confirmed()));
            },
            then: |result: SwalResult| {
                // this callback is called after `pre_confirm`,
                // only when the Swal is closing.
            },
            ..SwalOptions::default()
        });
    };

    view! {
        <button on:click=confirm>Click to confirm</button>
    }
}

To customize the behavior of the popup when performing asynchronous actions note that some useful methods exist to help you do that:

What about inputs?

SweetAlert allows you to use inputs directly in the popup. As of now, this feature is not supported the same way as SweetAlert does. To use inputs, you'd have to build the component on your own and insert it in the popup via the body field of SwalOptions. There is a good example that goes further into details.

What about Toasts?

SweetAlert comes with "toasts" which are small notifications appearing in a corner of the screen, usually for just a few seconds. This feature is not included in this crate and it will not get implemented. If you wish to use toasts, then there is already a great crate for this: leptos_toaster.

Create your own icons

You can create your own icons. There is already a full explanation in the documentation on crates.io.

Accessibility concerns

The popup follows the guidelines specified on accede-web.com.

To make sure the focus remains in the popup, all focusable elements within the popup must match this CSS query selector:

*:is(a[href], button, input, textarea, select, details):not([disabled]):not([aria-hidden=true]):not([inert]), [tabindex]:not([tabindex='-1'])

Note that inert will not work as expected. If you want to make several elements inert, then apply the attribute to each of them, but not to a common parent.

Note that elements with "display" set to "none" are also ignored.

Contributing

I'm new to Leptos and more generally to Rust so if you wish to contribute then please do so.

License

MIT License

Dependencies

~20–32MB
~524K SLoC