diff --git a/Cargo.toml b/Cargo.toml index fdaa195..cab8ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,11 @@ gloo = { version = "0.11.0", default-features = false, features = ["timers"], op wasm-bindgen = "0.2.99" web-sys = { version = "0.3.76", features = ["Window"] } yew = { version = "0.21.0", default-features = false, optional = true } +dioxus = { version = "0.6.1", optional = true } [features] yew = ["dep:yew", "gloo"] +dio = ["dioxus", "gloo"] [profile.release] opt-level = "z" diff --git a/DIOXUS.md b/DIOXUS.md new file mode 100644 index 0000000..a23f3b6 --- /dev/null +++ b/DIOXUS.md @@ -0,0 +1,176 @@ +## 🧬 Alert RS Dioxus Usage + +Adding Alert RS to your project is simple: + +1. Make sure your project is set up with **Dioxus**. Refer to the [Dioxus Getting Started Guide](https://dioxuslabs.com/learn/0.6/getting_started) for setup instructions. + +1. Add `alert-rs` to your dependencies: + + ```sh + cargo add alert-rs --features=dio + ``` + +1. Import `Alert` into your component and start enhancing your app's alert functionality. + +## 🛠️ Usage + +Incorporating the Dioxus Alert into your application is easy. Follow these steps: + +1. Import the Alert component into your project: + + ```rust + use dioxus::prelude::*; + use alert_rs::dioxus::Alert; + use alert_rs::{IconType, Position}; + ``` + +1. Define the alert properties and use the Alert component in your Dioxus component: + + ```rust + use dioxus::prelude::*; + use alert_rs::dioxus::Alert; + use alert_rs::{IconType, Position}; + + #[component] + pub fn App() -> Element { + let mut show_alert = use_signal(|| false); + + rsx! { + div { + button { + onclick: move |_| show_alert.set(true), + "Show Alert" + } + Alert { + title: "Alert Title", + body: "This is an alert message", + show_alert: show_alert, + timeout: 2500, + alert_class: "w-96 h-48 text-white", + icon_class: "flex justify-center", + confirm_button_text: "Okay", + cancel_button_text: "Cancel", + confirm_button_class: "bg-green-500 text-white rounded", + cancel_button_class: "bg-red-500 text-white rounded", + show_confirm_button: true, + show_cancel_button: true, + show_close_button: true, + on_confirm: move |_| { + // Your confirmation logic + }, + on_cancel: move |_| { + // Your cancel logic + }, + position: Position::TopRight, + icon_type: IconType::Success, + container_class: "flex items-center text-center justify-center bg-gray-800 text-white border border-gray-600", + title_class: "text-white", + body_class: "text-gray-300", + icon_color: "", + icon_width: "50", + } + } + } + } + ``` + +## 🔧 Props + +### Main Props + +| Property | Type | Description | Default | +| --------------------- | -------------- | ------------------------------------------------------------- | --------- | +| `show_alert` | `Signal` | The signal controlling the visibility of the alert. | `false` | +| `title` | `&'static str` | The title text for the alert. | `"Info"` | +| `body` | `&'static str` | The message content of the alert. | `""` | +| `timeout` | `u32` | Timeout duration in milliseconds for the alert to auto-close. | `2500` ms | +| `show_confirm_button` | `bool` | Whether to display the confirm button. | `true` | +| `show_cancel_button` | `bool` | Whether to display the cancel button. | `true` | +| `show_close_button` | `bool` | Whether to display the close button. | `false` | + +### Callback Props + +| Property | Type | Description | Default | +| ------------ | -------------- | ------------------------------------------------------ | ------- | +| `on_confirm` | `Callback<()>` | Callback triggered when the confirm button is clicked. | No-op | +| `on_cancel` | `Callback<()>` | Callback triggered when the cancel button is clicked. | No-op | +| `on_close` | `Callback<()>` | Callback triggered when the close button is clicked. | No-op | +| `will_open` | `Callback<()>` | Callback triggered before the alert opens. | No-op | +| `did_open` | `Callback<()>` | Callback triggered after the alert opens. | No-op | +| `did_close` | `Callback<()>` | Callback triggered after the alert closes. | No-op | + +### Alert Appearance & Positioning + +| Property | Type | Description | Default | +| ------------ | -------------- | --------------------------------------------------------------------- | ---------------- | +| `native` | `bool` | Whether to use the native browser alert instead of custom one. | `false` | +| `position` | `Position` | Position of the alert on the screen (`Position::TopRight`, etc.). | `TopRight` | +| `icon_type` | `IconType` | The type of icon to display with the alert (e.g., `Info`, `Warning`). | `IconType::Info` | +| `icon_color` | `&'static str` | The color of the icon. | `""` | +| `icon_width` | `&'static str` | The width of the icon. | `"50"` | + +### Styling Props + +```sh ++-----------------------------------------------------------+ <-- `alert_class` +| | +| +-----------------------------------------------+ | <-- `close_button_style` (if `show_close_button`) +| | [X] Close Button | | +| +-----------------------------------------------+ | +| | +| +-----------------------------------------------+ | <-- `icon_class` and `icon_style` +| | [Icon] | | <-- `icon_tag` +| +-----------------------------------------------+ | +| | +| +-----------------------------------------------+ | <-- `title_class` and `title_style` +| | [Alert Title] | | <-- `props.title` +| +-----------------------------------------------+ | +| | +| +-----------------------------------------------+ | <-- `separator_style` +| | [--- Separator ---] | | +| +-----------------------------------------------+ | +| | +| +-----------------------------------------------+ | <-- `message_style` and `body_class` +| | [Alert Message] | | <-- `props.body` +| +-----------------------------------------------+ | +| | +| +-----------------------------------------------+ | <-- `confirm_button_class` and `confirm_button_style` +| | [Confirm Button] | | <-- `props.confirm_button_text` +| +-----------------------------------------------+ | +| | +| +-----------------------------------------------+ | <-- `cancel_button_class` and `cancel_button_style` +| | [Cancel Button] | | <-- `props.cancel_button_text` +| +-----------------------------------------------+ | +| | ++-----------------------------------------------------------+ +``` + +| Property | Type | Description | Default | +| ---------------------- | -------------- | ---------------------------------------------------- | ------- | +| `alert_class` | `&'static str` | CSS class for styling the alert container. | `""` | +| `icon_class` | `&'static str` | CSS class for styling the icon. | `""` | +| `confirm_button_class` | `&'static str` | CSS class for styling the confirm button. | `""` | +| `cancel_button_class` | `&'static str` | CSS class for styling the cancel button. | `""` | +| `container_class` | `&'static str` | CSS class for styling the alert container. | `""` | +| `title_class` | `&'static str` | CSS class for styling the alert title. | `""` | +| `message_class` | `&'static str` | CSS class for styling the message text in the alert. | `""` | + +### Inline Styles + +| Property | Type | Description | Default | +| ---------------------- | -------------- | ----------------------------------------- | ------------------------------ | +| `alert_style` | `&'static str` | Inline CSS styles for the alert. | `DEFAULT_ALERT_STYLE` | +| `close_button_style` | `&'static str` | Inline CSS styles for the close button. | `DEFAULT_CLOSE_BUTTON_STYLE` | +| `confirm_button_style` | `&'static str` | Inline CSS styles for the confirm button. | `DEFAULT_CONFIRM_BUTTON_STYLE` | +| `cancel_button_style` | `&'static str` | Inline CSS styles for the cancel button. | `DEFAULT_CANCEL_BUTTON_STYLE` | +| `icon_style` | `&'static str` | Inline CSS styles for the icon. | `DEFAULT_ICON_STYLE` | +| `title_style` | `&'static str` | Inline CSS styles for the title text. | `DEFAULT_TITLE_STYLE` | +| `separator_style` | `&'static str` | Inline CSS styles for the separator. | `DEFAULT_SEPARATOR_STYLE` | +| `message_style` | `&'static str` | Inline CSS styles for the message text. | `DEFAULT_MESSAGE_STYLE` | + +## 💡 Notes + +- The `native` prop can be set to `true` to use the browser's default alert behavior instead of the custom component. +- The alert is displayed based on the `show_alert` signal, which should be controlled by the parent component. +- Timeout behavior can be adjusted using the `timeout` property, and alert visibility can be toggled using the `show_alert` state. +- You can customize the alert's appearance, including the icon, buttons, position, and styles. diff --git a/examples/dioxus/.gitignore b/examples/dioxus/.gitignore new file mode 100644 index 0000000..cc39038 --- /dev/null +++ b/examples/dioxus/.gitignore @@ -0,0 +1,2 @@ +target/**/* +dist/**/* diff --git a/examples/dioxus/Cargo.toml b/examples/dioxus/Cargo.toml new file mode 100755 index 0000000..eeb5cca --- /dev/null +++ b/examples/dioxus/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "alert-rs-dioxus-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +dioxus = { version = "0.6.1", features = ["web"] } +alert-rs = { path = "../../", features = ["dio"] } +dioxus-logger = "0.6.1" +regex = "1.11.1" + +[profile] + +[profile.wasm-dev] +inherits = "dev" +opt-level = 1 + +[profile.server-dev] +inherits = "dev" + +[profile.android-dev] +inherits = "dev" diff --git a/examples/dioxus/Dioxus.toml b/examples/dioxus/Dioxus.toml new file mode 100755 index 0000000..ebc603f --- /dev/null +++ b/examples/dioxus/Dioxus.toml @@ -0,0 +1,46 @@ +[application] + +# App (Project) Name +name = "alert-rs" + +# Dioxus App Default Platform +# desktop, web +default_platform = "web" + +# `build` & `serve` dist path +out_dir = "dist" + +# resource (assets) file folder +asset_dir = "assets" + +[web.app] + +# HTML title tag content +title = "alert-rs" + +[web.watcher] + +# when watcher trigger, regenerate the `index.html` +reload_html = true + +# which files or dirs will be watcher monitoring +watch_path = ["src", "assets"] + +# include `assets` in web platform +[web.resource] + +# CSS style file +style = [ + # online cdn. + "main.css", + "https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" +] + +# Javascript code file +script = [] + +[web.resource.dev] + +# Javascript code file +# serve: [dev-server] only +script = [] diff --git a/examples/dioxus/README.md b/examples/dioxus/README.md new file mode 100644 index 0000000..3372715 --- /dev/null +++ b/examples/dioxus/README.md @@ -0,0 +1,69 @@ +# 📚 Alert RS Dioxus Tailwind Components + +## 🛠️ Pre-requisites: + +### 🐧 **Linux Users** + +1. **Install [`rustup`](https://www.rust-lang.org/tools/install)**: + + ```sh + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + +1. Install [`Dioxus CLI`](https://dioxuslabs.com/learn/0.5/getting_started): + + ```sh + cargo install dioxus-cli + ``` + +### 🪟 **Windows Users** + +1. **Download and install `rustup`**: Follow the installation instructions [here](https://www.rust-lang.org/tools/install). + +1. **Install [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install)**: Open PowerShell as administrator and run: + + ```sh + wsl --install + ``` + +1. **Reset Network Stack**: In PowerShell (administrator mode), run: + + ```sh + netsh int ip reset all + netsh winsock reset + ``` + +1. **Install Linux packages in WSL**: Once inside your WSL terminal, update and install required dependencies: + + ```sh + sudo apt update + sudo apt install build-essential pkg-config libudev-dev + ``` + +1. Install [`Dioxus CLI`](https://dioxuslabs.com/learn/0.5/getting_started): + + ```sh + cargo install dioxus-cli + ``` + +## 🚀 Building and Running + +1. Fork/Clone the GitHub repository. + + ```sh + git clone https://github.com/opensass/alert-rs + ``` + +1. Navigate to the application directory. + + ```sh + cd alert-rs/examples/dioxus + ``` + +1. Run the client: + + ```sh + dx serve --port 3000 + ``` + +Navigate to http://localhost:3000 to explore the landing page. diff --git a/examples/dioxus/main.css b/examples/dioxus/main.css new file mode 100644 index 0000000..913598f --- /dev/null +++ b/examples/dioxus/main.css @@ -0,0 +1,8 @@ +body { + color: #5e5c7f; + background-color: #303030; + font-family: "Rubik", sans-serif; + font-size: 16px; + line-height: 1.7; + overflow-x: hidden; +} diff --git a/examples/dioxus/src/main.rs b/examples/dioxus/src/main.rs new file mode 100755 index 0000000..e679947 --- /dev/null +++ b/examples/dioxus/src/main.rs @@ -0,0 +1,357 @@ +use alert_rs::dioxus::Alert; +use alert_rs::{IconType, Position}; +use dioxus::prelude::*; +use dioxus_logger::tracing; + +fn main() { + dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger"); + tracing::info!("starting app"); + launch(app); +} + +#[component] +fn app() -> Element { + rsx! { + Home {} + } +} + +#[component] +pub fn Home() -> Element { + let mut show_alert_0 = use_signal(|| false); + let mut show_alert_1 = use_signal(|| false); + let mut show_alert_2 = use_signal(|| false); + let mut show_alert_3 = use_signal(|| false); + let mut show_alert_4 = use_signal(|| false); + let mut show_alert_5 = use_signal(|| false); + let mut show_alert_6 = use_signal(|| false); + let mut show_alert_7 = use_signal(|| false); + let mut show_alert_8 = use_signal(|| false); + let mut show_alert_9 = use_signal(|| false); + let mut show_alert_10 = use_signal(|| false); + let mut show_alert_11 = use_signal(|| false); + let mut show_alert_12 = use_signal(|| false); + + rsx! { + div { + class: "m-6 min-h-screen flex flex-col items-center justify-center", + h1 { + class: "text-3xl font-bold mb-8 text-white", + "Alert RS Dioxus Examples" + } + div { + class: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8", + + // Native Blocking Alert + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Native Blocking Alert" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Native Alert", + body: "This is a native blocking alert that exists in your browser.", + show_alert: show_alert_state, + native: true, +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_0.set(true), + "Show Alert" + } + Alert { + title: "Native Alert", + body: "This is a native blocking alert that exists in your browser.", + show_alert: show_alert_0, + native: true, + } + } + + // Top Right - Error With Confirmation + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Top Right - Error With Confirmation" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Error Alert", + body: "This is an error alert in the top-right corner.", + show_alert: show_alert_state, + position: Position::TopRight, + icon_type: IconType::Error, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + on_confirm: move |_| {{ show_second_alert.set(true); }}, +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_1.set(true), + "Show Alert" + } + Alert { + title: "Error Alert", + body: "This is an error alert in the top-right corner.", + show_alert: show_alert_1, + position: Position::TopRight, + icon_type: IconType::Error, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + on_confirm: move |_| show_alert_12.set(true), + } + Alert { + title: "Confirm Alert", + body: "Are you sure?", + show_alert: show_alert_12, + position: Position::TopRight, + icon_type: IconType::Error, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Bottom Left - Success + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Bottom Left - Success" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Success Alert", + body: "This is a success alert in the bottom-left corner.", + show_alert: show_alert_state, + position: Position::BottomLeft, + icon_type: IconType::Success, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_2.set(true), + "Show Alert" + } + Alert { + title: "Success Alert", + body: "This is a success alert in the bottom-left corner.", + show_alert: show_alert_2, + position: Position::BottomLeft, + icon_type: IconType::Success, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Bottom Right - Info + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Bottom Right - Info" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Info Alert", + body: "This is an info alert in the bottom-right corner.", + show_alert: show_alert_state, + position: Position::BottomRight, + icon_type: IconType::Info, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_3.set(true), + "Show Alert" + } + Alert { + title: "Info Alert", + body: "This is an info alert in the bottom-right corner.", + show_alert: show_alert_3, + position: Position::BottomRight, + icon_type: IconType::Info, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Bottom Left - Warning + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Bottom Left - Warning" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Warning Alert", + body: "This is a warning alert in the bottom-left corner.", + show_alert: show_alert_7, + position: Position::BottomLeft, + icon_type: IconType::Warning, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_7.set(true), + "Show Alert" + } + Alert { + title: "Warning Alert", + body: "This is a warning alert in the bottom-left corner.", + show_alert: show_alert_7, + position: Position::BottomLeft, + icon_type: IconType::Warning, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Centered - Success + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Centered - Success" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Centered Success Alert", + body: "This is a centered success alert.", + show_alert: show_alert_8, + position: Position::Center, + icon_type: IconType::Success, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_8.set(true), + "Show Alert" + } + Alert { + title: "Centered Success Alert", + body: "This is a centered success alert.", + show_alert: show_alert_8, + position: Position::Center, + icon_type: IconType::Success, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Bottom Right - Warning + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Bottom Right - Warning" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Warning Alert", + body: "This is a warning alert in the bottom-right corner.", + show_alert: show_alert_9, + position: Position::BottomRight, + icon_type: IconType::Warning, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_9.set(true), + "Show Alert" + } + Alert { + title: "Warning Alert", + body: "This is a warning alert in the bottom-right corner.", + show_alert: show_alert_9, + position: Position::BottomRight, + icon_type: IconType::Warning, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Top Center - Error + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Top Center - Error" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Error Alert", + body: "This is an error alert in the top-center of the screen.", + show_alert: show_alert_10, + position: Position::TopCenter, + icon_type: IconType::Error, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_10.set(true), + "Show Alert" + } + Alert { + title: "Error Alert", + body: "This is an error alert in the top-center of the screen.", + show_alert: show_alert_10, + position: Position::TopCenter, + icon_type: IconType::Error, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + + // Top Left - Info + div { + class: "flex flex-col items-center bg-gray-200 p-4 rounded-lg shadow-md", + h2 { class: "text-xl font-bold mb-2", "Top Left - Info" } + pre { + class: "font-mono text-xs text-white p-4 bg-gray-800 rounded-md w-full overflow-x-auto", + r#"Alert \{{ + title: "Info Alert", + body: "This is an info alert in the top-left corner.", + show_alert: show_alert_11, + position: Position::TopLeft, + icon_type: IconType::Info, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", +\}}"# + } + button { + class: "mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", + onclick: move |_| show_alert_11.set(true), + "Show Alert" + } + Alert { + title: "Info Alert", + body: "This is an info alert in the top-left corner.", + show_alert: show_alert_11, + position: Position::TopLeft, + icon_type: IconType::Info, + alert_class: "text-center w-96 h-48 bg-white text-black rounded-md shadow-lg p-4", + title_class: "text-sm font-semibold", + body_class: "text-xs", + } + } + } + } + } +} \ No newline at end of file diff --git a/src/dioxus.rs b/src/dioxus.rs new file mode 100644 index 0000000..af45922 --- /dev/null +++ b/src/dioxus.rs @@ -0,0 +1,582 @@ +use crate::common::*; +use dioxus::prelude::*; +use gloo::timers::callback::Timeout; +use web_sys::window; + +/// Properties for configuring the `Alert` component. +/// +/// This component supports customizable alerts with flexible behaviors, icons, buttons, +/// and styling options for use in various notification or messaging contexts. +#[derive(Props, Clone, PartialEq)] +pub struct AlertProps { + /// The body text displayed in the alert. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub body: &'static str, + + /// A signal controlling the visibility of the alert. + /// + /// The alert will show or hide based on this signal. + pub show_alert: Signal, + + /// The duration in milliseconds before the alert automatically closes. + /// + /// Defaults to `2500` milliseconds. + #[props(default = 2500)] + pub timeout: u32, + + /// The title text displayed at the top of the alert. + /// + /// Defaults to `"Info"`. + #[props(default = "Info")] + pub title: &'static str, + + /// The text displayed on the confirm button. + /// + /// Defaults to `"Okay"`. + #[props(default = "Okay")] + pub confirm_button_text: &'static str, + + /// The text displayed on the cancel button. + /// + /// Defaults to `"Cancel"`. + #[props(default = "Cancel")] + pub cancel_button_text: &'static str, + + /// Whether to show the confirm button. + /// + /// Defaults to `true`. + #[props(default = true)] + pub show_confirm_button: bool, + + /// Whether to show the cancel button. + /// + /// Defaults to `true`. + #[props(default = true)] + pub show_cancel_button: bool, + + /// Whether to show the close button. + /// + /// Defaults to `false`. + #[props(default = false)] + pub show_close_button: bool, + + /// The position of the alert on the screen. + /// + /// Defaults to `Position::TopRight`. + #[props(default = Position::TopRight)] + pub position: Position, + + /// The icon type displayed in the alert. + /// + /// Defaults to `IconType::Info`. + #[props(default = IconType::Info)] + pub icon_type: IconType, + + /// The color of the icon. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub icon_color: &'static str, + + /// The width of the icon in the alert. + /// + /// Defaults to `"50"`. + #[props(default = "50")] + pub icon_width: &'static str, + + /// Additional CSS classes for the alert container. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub alert_class: &'static str, + + /// Additional CSS classes for the icon in the alert. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub icon_class: &'static str, + + /// Additional CSS classes for the confirm button. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub confirm_button_class: &'static str, + + /// Additional CSS classes for the cancel button. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub cancel_button_class: &'static str, + + /// Additional CSS classes for the container wrapping the alert. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub container_class: &'static str, + + /// Additional CSS classes for the alert title. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub title_class: &'static str, + + /// Additional CSS classes for the alert body. + /// + /// Defaults to an empty string. + #[props(default = "")] + pub body_class: &'static str, + + /// Inline styles for the alert container. + /// + /// Defaults to `DEFAULT_ALERT_STYLE`. + #[props(default = DEFAULT_ALERT_STYLE)] + pub alert_style: &'static str, + + /// Inline styles for the close button. + /// + /// Defaults to `DEFAULT_CLOSE_BUTTON_STYLE`. + #[props(default = DEFAULT_CLOSE_BUTTON_STYLE)] + pub close_button_style: &'static str, + + /// Inline styles for the confirm button. + /// + /// Defaults to `DEFAULT_CONFIRM_BUTTON_STYLE`. + #[props(default = DEFAULT_CONFIRM_BUTTON_STYLE)] + pub confirm_button_style: &'static str, + + /// Inline styles for the cancel button. + /// + /// Defaults to `DEFAULT_CANCEL_BUTTON_STYLE`. + #[props(default = DEFAULT_CANCEL_BUTTON_STYLE)] + pub cancel_button_style: &'static str, + + /// Inline styles for the icon in the alert. + /// + /// Defaults to `DEFAULT_ICON_STYLE`. + #[props(default = DEFAULT_ICON_STYLE)] + pub icon_style: &'static str, + + /// Inline styles for the alert title. + /// + /// Defaults to `DEFAULT_TITLE_STYLE`. + #[props(default = DEFAULT_TITLE_STYLE)] + pub title_style: &'static str, + + /// Inline styles for the separator between title and body. + /// + /// Defaults to `DEFAULT_SEPARATOR_STYLE`. + #[props(default = DEFAULT_SEPARATOR_STYLE)] + pub separator_style: &'static str, + + /// Inline styles for the alert body message. + /// + /// Defaults to `DEFAULT_MESSAGE_STYLE`. + #[props(default = DEFAULT_MESSAGE_STYLE)] + pub message_style: &'static str, + + /// Whether to use the native browser alert. + /// + /// Defaults to `false`. + #[props(default = false)] + pub native: bool, + + /// Callback triggered before the alert opens. + /// + /// Defaults to an empty callback. + #[props(default)] + pub will_open: Callback<()>, + + /// Callback triggered after the alert opens. + /// + /// Defaults to an empty callback. + #[props(default)] + pub did_open: Callback<()>, + + /// Callback triggered after the alert closes. + /// + /// Defaults to an empty callback. + #[props(default)] + pub did_close: Callback<()>, + + /// Callback triggered when the confirm button is clicked. + /// + /// Defaults to an empty callback. + #[props(default)] + pub on_confirm: Callback<()>, + + /// Callback triggered when the close button is clicked. + /// + /// Defaults to an empty callback. + #[props(default)] + pub on_close: Callback<()>, + + /// Callback triggered when the cancel button is clicked. + /// + /// Defaults to an empty callback. + #[props(default)] + pub on_cancel: Callback<()>, +} + +/// Alert Component +/// +/// A Dioxus component for displaying customizable alerts with a range of styling and behavioral options. +/// This `Alert` component allows you to show notifications, warnings, or messages with tailored +/// icons, buttons, and positioning. It is a highly flexible solution for user-facing alerts. +/// +/// # Properties +/// The component uses the `AlertProps` struct for configuration. Key properties include: +/// +/// - **body**: The text content of the alert message (`&'static str`). Default: `""`. +/// - **show_alert**: A `Signal` controlling the alert's visibility. This is a required prop. +/// - **timeout**: The duration in milliseconds before the alert auto-closes (`u32`). Default: `2500`. +/// - **title**: The heading text for the alert (`&'static str`). Default: `"Info"`. +/// - **confirm_button_text**: The text for the confirm button (`&'static str`). Default: `"Okay"`. +/// - **cancel_button_text**: The text for the cancel button (`&'static str`). Default: `"Cancel"`. +/// - **show_confirm_button**: Determines whether the confirm button is visible (`bool`). Default: `true`. +/// - **show_cancel_button**: Determines whether the cancel button is visible (`bool`). Default: `true`. +/// - **show_close_button**: Determines whether a close button is included (`bool`). Default: `false`. +/// - **position**: The screen position of the alert (`Position`). Default: `Position::TopRight`. +/// - **icon_type**: The type of icon to display (`IconType`). Default: `IconType::Info`. +/// - **icon_color**: CSS color for the alert icon (`&'static str`). Default: `""`. +/// - **icon_width**: Width of the alert icon (`&'static str`). Default: `"50"`. +/// - **alert_class**: Custom CSS class for the alert container (`&'static str`). Default: `""`. +/// - **icon_class**: Custom CSS class for the alert icon (`&'static str`). Default: `""`. +/// - **confirm_button_class**: Custom CSS class for the confirm button (`&'static str`). Default: `""`. +/// - **cancel_button_class**: Custom CSS class for the cancel button (`&'static str`). Default: `""`. +/// - **container_class**: Custom CSS class for the alert's container (`&'static str`). Default: `""`. +/// - **title_class**: Custom CSS class for the alert title (`&'static str`). Default: `""`. +/// - **body_class**: Custom CSS class for the alert body (`&'static str`). Default: `""`. +/// - **alert_style**: Inline style for the alert component (`&'static str`). Default: `DEFAULT_ALERT_STYLE`. +/// - **close_button_style**: Inline style for the close button (`&'static str`). Default: `DEFAULT_CLOSE_BUTTON_STYLE`. +/// - **confirm_button_style**: Inline style for the confirm button (`&'static str`). Default: `DEFAULT_CONFIRM_BUTTON_STYLE`. +/// - **cancel_button_style**: Inline style for the cancel button (`&'static str`). Default: `DEFAULT_CANCEL_BUTTON_STYLE`. +/// - **icon_style**: Inline style for the alert icon (`&'static str`). Default: `DEFAULT_ICON_STYLE`. +/// - **title_style**: Inline style for the alert title (`&'static str`). Default: `DEFAULT_TITLE_STYLE`. +/// - **separator_style**: Inline style for the separator line (`&'static str`). Default: `DEFAULT_SEPARATOR_STYLE`. +/// - **message_style**: Inline style for the alert message text (`&'static str`). Default: `DEFAULT_MESSAGE_STYLE`. +/// - **native**: If `true`, uses the browser's native alert instead of the custom component (`bool`). Default: `false`. +/// - **will_open**: Callback invoked before the alert is displayed (`Callback<()>`). Default: no-op. +/// - **did_open**: Callback invoked after the alert is displayed (`Callback<()>`). Default: no-op. +/// - **did_close**: Callback invoked after the alert is closed (`Callback<()>`). Default: no-op. +/// - **on_confirm**: Callback invoked when the confirm button is clicked (`Callback<()>`). Default: no-op. +/// - **on_close**: Callback invoked when the close button is clicked (`Callback<()>`). Default: no-op. +/// - **on_cancel**: Callback invoked when the cancel button is clicked (`Callback<()>`). Default: no-op. +/// +/// # Features +/// - Highly customizable appearance and behavior. +/// - Supports dynamic positioning and icon configuration. +/// - Configurable buttons for confirmation, cancellation, and closing. +/// - Optional timeout for auto-closing the alert. +/// - Native or custom alert rendering options. +/// - Built-in callback support for interactive handling of user actions. +/// +/// # Examples +/// +/// ## Basic Alert +/// ```rust +/// use dioxus::prelude::*; +/// use alert_rs::dioxus::Alert; +/// +/// fn App() -> Element { +/// let mut show_alert = use_signal(|| false); +/// +/// rsx! { +/// button { +/// onclick: move |_| show_alert.set(true), +/// "Show Alert" +/// } +/// Alert { +/// show_alert: show_alert.clone(), +/// title: "Hello", +/// body: "This is a basic alert.", +/// } +/// } +/// } +/// ``` +/// +/// ## Customized Alert with Buttons +/// ```rust +/// use dioxus::prelude::*; +/// use alert_rs::dioxus::Alert; +/// +/// fn App() -> Element { +/// let mut show_alert = use_signal(|| false); +/// +/// rsx! { +/// button { +/// onclick: move |_| show_alert.set(true), +/// "Custom Alert" +/// } +/// Alert { +/// show_alert: show_alert.clone(), +/// title: "Alert Title", +/// body: "This is a custom alert with buttons.", +/// confirm_button_text: "Confirm", +/// cancel_button_text: "Cancel", +/// on_confirm: |_| println!("Confirmed!"), +/// on_cancel: |_| println!("Cancelled!"), +/// } +/// } +/// } +/// ``` +/// +/// ## Native Alert +/// ```rust +/// use dioxus::prelude::*; +/// use alert_rs::dioxus::Alert; +/// +/// fn App() -> Element { +/// let mut show_alert = use_signal(|| false); +/// +/// rsx! { +/// button { +/// onclick: move |_| show_alert.set(true), +/// "Native Alert" +/// } +/// Alert { +/// show_alert: show_alert.clone(), +/// native: true, +/// body: "This uses the browser's native alert.", +/// } +/// } +/// } +/// ``` +#[component] +pub fn Alert(props: AlertProps) -> Element { + let timeout = props.timeout; + let native = props.native; + let mut show_alert = props.show_alert; + + let title = props.title.to_string(); + let body = props.body.to_string(); + + let show_confirm_button = props.show_confirm_button; + let show_cancel_button = props.show_cancel_button; + let show_close_button = props.show_close_button; + let icon_color = props.icon_color; + let icon_type = props.icon_type; + let icon_width = props.icon_width; + let icon_style = props.icon_style; + + use_effect(move || { + if show_alert() && !native { + props.will_open.call(()); + + let handle = Timeout::new(timeout, move || { + show_alert.set(false); + props.did_close.call(()); + }) + .forget(); + + props.did_open.call(()); + } else if show_alert() && native { + if let Some(win) = window() { + props.will_open.call(()); + + let full_message = if !title.is_empty() { + format!("{}\n\n{}", title, body) + } else { + body.clone() + }; + + match (show_confirm_button, show_cancel_button, show_close_button) { + (true, true, true) => { + if win.confirm_with_message(&full_message).unwrap_or(false) { + props.on_confirm.call(()); + } else { + props.on_close.call(()); + props.on_cancel.call(()); + } + } + (true, true, false) => { + if win.confirm_with_message(&full_message).unwrap_or(false) { + props.on_confirm.call(()); + } else { + props.on_cancel.call(()); + } + } + (true, false, false) => { + win.alert_with_message(&full_message).ok(); + props.on_confirm.call(()); + } + _ => {} + } + + Timeout::new(timeout, move || { + show_alert.set(false); + props.did_close.call(()); + }) + .forget(); + + props.did_open.call(()); + } + } + }); + + let position_style = match props.position { + Position::TopLeft => "top: 0; left: 0;", + Position::TopCenter => "top: 0; left: 50%; transform: translateX(-50%);", + Position::TopRight => "top: 0; right: 0;", + Position::LeftCenter => "top: 50%; left: 0; transform: translateY(-50%);", + Position::Center => "top: 50%; left: 50%; transform: translate(-50%, -50%);", + Position::BottomCenter => "bottom: 0; left: 50%; transform: translateX(-50%);", + Position::RightCenter => "top: 50%; right: 0; transform: translateY(-50%);", + Position::BottomRight => "bottom: 0; right: 0;", + Position::BottomLeft => "bottom: 0; left: 0;", + Position::Custom(x, y) => &format!("top: {}; left: {};", y, x), + }; + + let icon_color = if icon_color.is_empty() { + match icon_type { + IconType::Warning => "orange", + IconType::Error => "red", + IconType::Success => "green", + IconType::Info => "blue", + IconType::Question => "gray", + } + } else { + props.icon_color + }; + + // SVGs taken from: https://fontawesome.com/icons + let icon_tag = match icon_type { + IconType::Warning => rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + width: "{icon_width}", + style: "{icon_style}", + class: "p-2 m-2", + fill: "{icon_color}", + view_box: "0 0 512 512", + path { + d: "M248.4 84.3c1.6-2.7 4.5-4.3 7.6-4.3s6 1.6 7.6 4.3L461.9 410c1.4 2.3 2.1 4.9 2.1 7.5c0 8-6.5 14.5-14.5 14.5H62.5c-8 0-14.5-6.5-14.5-14.5c0-2.7 .7-5.3 2.1-7.5L248.4 84.3zm-41-25L9.1 385c-6 9.8-9.1 21-9.1 32.5C0 452 28 480 62.5 480h387c34.5 0 62.5-28 62.5-62.5c0-11.5-3.2-22.7-9.1-32.5L304.6 59.3C294.3 42.4 275.9 32 256 32s-38.3 10.4-48.6 27.3zM288 368a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm-8-184c0-13.3-10.7-24-24-24s-24 10.7-24 24v96c0 13.3 10.7 24 24 24s24-10.7 24-24V184z" + } + } + }, + IconType::Error => rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + width: "{icon_width}", + style: "{icon_style}", + class: "p-2 m-2", + fill: "{icon_color}", + view_box: "0 0 20 20", + path { + d: "M12.71,7.291c-0.15-0.15-0.393-0.15-0.542,0L10,9.458L7.833,7.291c-0.15-0.15-0.392-0.15-0.542,0c-0.149,0.149-0.149,0.392,0,0.541L9.458,10l-2.168,2.167c-0.149,0.15-0.149,0.393,0,0.542c0.15,0.149,0.392,0.149,0.542,0L10,10.542l2.168,2.167c0.149,0.149,0.392,0.149,0.542,0c0.148-0.149,0.148-0.392,0-0.542L10.542,10l2.168-2.168C12.858,7.683,12.858,7.44,12.71,7.291z M10,1.188c-4.867,0-8.812,3.946-8.812,8.812c0,4.867,3.945,8.812,8.812,8.812s8.812-3.945,8.812-8.812C18.812,5.133,14.867,1.188,10,1.188z M10,18.046c-4.444,0-8.046-3.603-8.046-8.046c0-4.444,3.603-8.046,8.046-8.046c4.443,0,8.046,3.602,8.046,8.046C18.046,14.443,14.443,18.046,10,18.046z" + } + } + }, + IconType::Success => rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + width: "{icon_width}", + style: "{icon_style}", + class: "p-2 m-2", + fill: "{icon_color}", + view_box: "0 0 512 512", + path { + d: "M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L369 209z" + } + } + }, + IconType::Info => rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + width: "{icon_width}", + style: "{icon_style}", + class: "p-2 m-2", + fill: "{icon_color}", + view_box: "0 0 16 16", + path { d: "M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" } + path { + d: "m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" + } + } + }, + IconType::Question => rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + width: "{icon_width}", + style: "{icon_style}", + class: "p-2 m-2", + fill: "{icon_color}", + view_box: "0 0 16 16", + path { d: "M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" } + path { + d: "M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" + } + } + }, + }; + + let on_cancel = { + move |_| { + props.on_cancel.call(()); + show_alert.set(false); + } + }; + + let on_confirm = { + move |_| { + props.on_confirm.call(()); + } + }; + + if !native { + rsx! { + if show_alert() { + div { + style: props.alert_style, + div { + class: props.alert_class, + style: format!("position: absolute; {}", position_style), + if show_close_button { + button { + style: props.close_button_style, + onclick: move |_| { + props.on_cancel.call(()); + show_alert.set(false); + }, + "X" + } + } + div { + class: props.icon_class, + style: props.icon_style, + {icon_tag} + } + strong { + class: props.title_class, + style: props.title_style, + "{props.title}" + } + hr { style: props.separator_style } + p { + class: props.body_class, + style: props.message_style, + "{props.body}" + } + if props.show_confirm_button { + button { + class: props.confirm_button_class, + style: props.confirm_button_style, + onclick: on_confirm, + "{props.confirm_button_text}" + } + } + if props.show_cancel_button { + button { + class: props.cancel_button_class, + style: props.cancel_button_style, + onclick:on_cancel, + "{props.cancel_button_text}" + } + } + } + } + } + } + } else {rsx!{}} +} diff --git a/src/lib.rs b/src/lib.rs index 865d992..4ab8f32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,4 +10,7 @@ pub mod common; #[cfg(feature = "yew")] pub mod yew; +#[cfg(feature = "dio")] +pub mod dioxus; + pub use common::{IconType, Position};