From 0c20edba6f026155e7bee5e3237f8dd54bcbe328 Mon Sep 17 00:00:00 2001 From: Mahmoud Date: Thu, 19 Dec 2024 00:47:57 +0200 Subject: [PATCH] feat: add dioxus support --- Cargo.toml | 6 +- DIOXUS.md | 87 ++++++++++ README.md | 79 +-------- YEW.md | 73 +++++++++ examples/dioxus/.gitignore | 2 + examples/dioxus/Cargo.toml | 22 +++ examples/dioxus/Dioxus.toml | 48 ++++++ examples/dioxus/README.md | 69 ++++++++ examples/dioxus/src/main.rs | 144 +++++++++++++++++ examples/yew/dist | 1 + examples/yew/src/pages/home.rs | 6 +- src/common.rs | 10 ++ src/dioxus.rs | 283 +++++++++++++++++++++++++++++++++ src/lib.rs | 7 + src/yew.rs | 27 ++-- 15 files changed, 769 insertions(+), 95 deletions(-) create mode 100644 DIOXUS.md create mode 100644 YEW.md create mode 100644 examples/dioxus/.gitignore create mode 100755 examples/dioxus/Cargo.toml create mode 100755 examples/dioxus/Dioxus.toml create mode 100644 examples/dioxus/README.md create mode 100755 examples/dioxus/src/main.rs create mode 160000 examples/yew/dist create mode 100644 src/common.rs create mode 100644 src/dioxus.rs diff --git a/Cargo.toml b/Cargo.toml index eadfe2b..994d0c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,13 @@ exclude = ["assets", "examples"] [dependencies] wasm-bindgen = "0.2.99" web-sys = { version = "0.3.76", features = ["Window", "ScrollToOptions", "ScrollBehavior", "DomRect", "Element"] } -gloo = { version = "0.11.0", features = ["utils"], optional = true } +gloo = { version = "0.11.0", features = ["utils"] } yew = { version = "0.21.0", default-features = false, optional = true } +dioxus = { version = "0.6.1", optional = true } [features] -yew = ["dep:yew", "gloo"] +yew = ["dep:yew"] +dio = ["dioxus", ] [profile.release] opt-level = "z" diff --git a/DIOXUS.md b/DIOXUS.md new file mode 100644 index 0000000..568e596 --- /dev/null +++ b/DIOXUS.md @@ -0,0 +1,87 @@ +## 🧬 Scroll RS Dioxus Usage + +Adding Scroll-RS to your Dioxus project is easy: + +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 `scroll-rs` to your dependencies: + + ```sh + cargo add scroll-rs --features=dio + ``` + +1. Import `Scroll` into your component and start enhancing your app's scroll functionality. + +## 🛠️ Usage + +Here's an example of integrating `scroll-rs` into your Dioxus app: + +```rust +use scroll_rs::dioxus::Scroll; +use scroll_rs::Behavior; +use dioxus::prelude::*; + +#[component] +pub fn Home() -> Element { + rsx! { + div { + class: "min-h-screen bg-gray-900 text-white p-8", + h1 { + class: "text-4xl font-bold text-center mb-8", + "Scroll-RS Demo" + } + + // Scrollable content + div { + id: "top", + class: "h-96 bg-gray-700 p-8 text-center", + h2 { + class: "text-3xl font-bold", + "Top of the Page" + } + p { "Scroll down to see buttons in action!" } + } + + div { + id: "bottom", + class: "h-96 bg-gray-800 p-8 text-center", + h2 { + class: "text-3xl font-bold", + "Bottom of the Page" + } + p { "You reached the bottom!" } + } + + // Scroll components + Scroll { + style: "position: fixed; bottom: 2rem; right: 2rem; background: #10B981; padding: 1rem; border-radius: 50%;", + icon: rsx! { span { "↑" } }, + scroll_id: "top" + } + Scroll { + style: "position: fixed; bottom: 2rem; left: 2rem; background: #F59E0B; padding: 1rem; border-radius: 50%;", + icon: rsx! { span { "↓" } }, + scroll_id: "bottom" + } + } + } +} +``` + +## 🔧 Props + +| Property | Type | Description | Default | +| ------------- | -------------- | ------------------------------------------------------------------ | --------------- | +| `style` | `&'static str` | Inline CSS styles for the scroll button. | Default styling | +| `class` | `&'static str` | Custom CSS classes for styling the button. | None | +| `icon` | `Element` | Custom icon (HTML/SVG) for the scroll button. | Default SVG | +| `behavior` | `Behavior` | Scrolling behavior: `Smooth`, `Instant`. | `Smooth` | +| `top` | `f64` | Target top position for scrolling. | `0.0` | +| `left` | `f64` | Target left position for scrolling (horizontal scrolling). | `0.0` | +| `offset` | `f64` | Offset to apply when scrolling to the target position. | `0.0` | +| `delay` | `u32` | Delay (in ms) before initiating the scroll. | `0` | +| `auto_hide` | `bool` | Whether to hide the button automatically based on scroll position. | `true` | +| `threshold` | `f64` | Scroll threshold to determine button visibility. | `20.0` px | +| `update_hash` | `bool` | Whether to update the URL hash during scrolling. | `true` | +| `show_id` | `&'static str` | ID of the target element for the scroll button visibility logic. | None | +| `scroll_id` | `&'static str` | ID of the target container to scroll to. | None | diff --git a/README.md b/README.md index a97bb69..da7737e 100644 --- a/README.md +++ b/README.md @@ -33,78 +33,13 @@ The following are some of the reasons why Scroll-RS is a great addition to your 1. **👀 Auto-Hide**: Automatically hide or show based on user-defined thresholds. 1. **🔧 Flexible Offsets**: Adjust scrolling positions and delays with ease. -## ⚙️ Yew Installation - -Adding Scroll-RS to your project is simple: - -1. Ensure your project is set up with a Wasm-based framework like **Yew**. Refer to their [Getting Started Guide](https://yew.rs/docs/getting-started/introduction) for setup instructions. - -1. Add `scroll-rs` to your dependencies: - - ```sh - cargo add scroll-rs --features=yew - ``` - -1. Import `Scroll` into your component and start enhancing your app's scroll functionality. - -## 🛠️ Usage - -Below is an example of how to integrate `Scroll-RS` into your Yew app: - -```rust -use scroll_rs::yew::{Behavior, Scroll}; -use yew::prelude::*; - -#[function_component(Home)] -pub fn home() -> Html { - html! { -
-

{ "Scroll-RS Demo" }

- - // Scrollable content -
-

{ "Top of the Page" }

-

{ "Scroll down to see buttons in action!" }

-
- -
-

{ "Bottom of the Page" }

-

{ "You reached the bottom!" }

-
- - // Scroll components - {"↑"} }} - scroll_id="top" - /> - {"↓"} }} - scroll_id="bottom" - /> -
- } -} -``` - -## 🔧 Props - -| Property | Type | Description | Default | -|----------------|-------------------|--------------------------------------------------------------------|-------------------| -| `style` | `&'static str` | Inline CSS styles for the scroll button. | Default styling | -| `class` | `&'static str` | Custom CSS classes for styling the button. | None | -| `content` | `Html` | Custom content (HTML/SVG) for the scroll button. | Default SVG | -| `behavior` | `Behavior` | Scrolling behavior: `Smooth`, `Instant`. | `Smooth` | -| `top` | `f64` | Target top position for scrolling. | `0.0` | -| `left` | `f64` | Target left position for scrolling (horizontal scrolling). | `0.0` | -| `offset` | `f64` | Offset to apply when scrolling to the target position. | `0.0` | -| `delay` | `u32` | Delay (in ms) before initiating the scroll. | `0` | -| `auto_hide` | `bool` | Whether to hide the button automatically based on scroll position.| `true` | -| `threshold` | `f64` | Scroll threshold to determine button visibility. | `20.0` px | -| `update_hash` | `bool` | Whether to update the URL hash during scrolling. | `true` | -| `show_id` | `&'static str` | ID of the target element for the scroll button visibility logic. | None | -| `scroll_id` | `&'static str` | ID of the target container to scroll to. | None | +## Y Yew Usage + +Refer to [our guide](YEW.md) to integrate this component into your Yew app. + +## 🧬 Dioxus Usage + +Refer to [our guide](DIOXUS.md) to integrate this component into your Dioxus app. ## 🤝 Contributions diff --git a/YEW.md b/YEW.md new file mode 100644 index 0000000..9d40255 --- /dev/null +++ b/YEW.md @@ -0,0 +1,73 @@ +## Y Scroll RS Yew Usage + +Adding Scroll-RS to your project is simple: + +1. Make sure your project is set up with **Yew**. Refer to their [Getting Started Guide](https://yew.rs/docs/getting-started/introduction) for setup instructions. + +1. Add `scroll-rs` to your dependencies: + + ```sh + cargo add scroll-rs --features=yew + ``` + +1. Import `Scroll` into your component and start enhancing your app's scroll functionality. + +## 🛠️ Usage + +Below is an example of how to integrate `scroll-rs` into your Yew app: + +```rust +use scroll_rs::yew::Scroll; +use scroll_rs::Behavior; +use yew::prelude::*; + +#[function_component(Home)] +pub fn home() -> Html { + html! { +
+

{ "Scroll-RS Demo" }

+ + // Scrollable content +
+

{ "Top of the Page" }

+

{ "Scroll down to see buttons in action!" }

+
+ +
+

{ "Bottom of the Page" }

+

{ "You reached the bottom!" }

+
+ + // Scroll components + {"↑"} }} + scroll_id="top" + /> + {"↓"} }} + scroll_id="bottom" + /> +
+ } +} +``` + +## 🔧 Props + +| Property | Type | Description | Default | +| ------------- | -------------- | ------------------------------------------------------------------ | --------------- | +| `style` | `&'static str` | Inline CSS styles for the scroll button. | Default styling | +| `class` | `&'static str` | Custom CSS classes for styling the button. | None | +| `icon` | `Html` | Custom icon (HTML/SVG) for the scroll button. | Default SVG | +| `behavior` | `Behavior` | Scrolling behavior: `Smooth`, `Instant`. | `Smooth` | +| `top` | `f64` | Target top position for scrolling. | `0.0` | +| `left` | `f64` | Target left position for scrolling (horizontal scrolling). | `0.0` | +| `offset` | `f64` | Offset to apply when scrolling to the target position. | `0.0` | +| `delay` | `u32` | Delay (in ms) before initiating the scroll. | `0` | +| `auto_hide` | `bool` | Whether to hide the button automatically based on scroll position. | `true` | +| `threshold` | `f64` | Scroll threshold to determine button visibility. | `20.0` px | +| `update_hash` | `bool` | Whether to update the URL hash during scrolling. | `true` | +| `show_id` | `&'static str` | ID of the target element for the scroll button visibility logic. | None | +| `scroll_id` | `&'static str` | ID of the target container to scroll to. | None | 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..63e0e34 --- /dev/null +++ b/examples/dioxus/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "scroll-rs-dioxus-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +dioxus = { version = "0.6.1", features = ["web"] } +scroll-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..ba41305 --- /dev/null +++ b/examples/dioxus/Dioxus.toml @@ -0,0 +1,48 @@ +[application] + +# App (Project) Name +name = "input-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 = "input-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. + "https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" +] + +# Javascript code file +script = [ + # online cdn. + "https://kit.fontawesome.com/8f223ead6e.js" +] + +[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..b8d6564 --- /dev/null +++ b/examples/dioxus/README.md @@ -0,0 +1,69 @@ +# 📚 Scroll 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/scroll-rs + ``` + +1. Navigate to the application directory. + + ```sh + cd scroll-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/src/main.rs b/examples/dioxus/src/main.rs new file mode 100755 index 0000000..d505062 --- /dev/null +++ b/examples/dioxus/src/main.rs @@ -0,0 +1,144 @@ +use dioxus::prelude::*; +use dioxus_logger::tracing; +use scroll_rs::dioxus::Scroll; +use scroll_rs::Behavior; + +fn main() { + dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger"); + tracing::info!("starting app"); + launch(app); +} + +fn app() -> Element { + rsx! { + Home {} + } +} + +#[component] +pub fn Home() -> Element { + rsx! { + div { + class: "p-8 bg-gray-900 text-white min-h-screen relative", + h1 { + class: "text-4xl font-bold mb-8 text-center", + "Scroll RS Dioxus Demo" + } + // Content for Scrolling + div { + id: "top", + class: "h-96 bg-gray-700 mt-16 p-8 text-center", + h2 { + class: "text-3xl font-bold", + "Top of the Page" + } + p { "Scroll down to interact with the buttons." } + } + div { id: "left-scroll", class: "w-[2000px] h-96 bg-gray-800 mt-16" } + div { + id: "bottom-scroll", + class: "h-96 bg-gray-700 mt-16 p-8 text-center", + h2 { + class: "text-3xl font-bold", + "Bottom of the Page" + } + p { "You have reached the bottom!" } + } + div { + // Default Scroll Button + div { + title: "Default Scroll Button", + Scroll {} + } + // Scroll to Bottom + div { + title: "Scroll to Bottom", + Scroll { + style: "position: fixed; bottom: 4rem; right: 3rem; background-color: #10B981; color: #FFFFFF; padding: 1rem; border-radius: 50%; cursor: pointer; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.4);", + icon: rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + view_box: "0 0 24 24", + stroke: "currentColor", + style: "width: 24px; height: 24px;", + path { + stroke_linecap: "round", + stroke_linejoin: "round", + stroke_width: "2", + d: "M19 9l-7 7-7-7", + } + } + }, + scroll_id: "bottom-scroll", + } + } + // Scroll Left + div { + title: "Scroll to the Left", + Scroll { + style: "position: fixed; top: 40%; left: 2rem; background-color: #E11D48; color: #FFFFFF; padding: 1rem; border-radius: 50%; cursor: pointer; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.4);", + icon: rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + view_box: "0 0 24 24", + stroke: "currentColor", + style: "width: 24px; height: 24px;", + path { + stroke_linecap: "round", + stroke_linejoin: "round", + stroke_width: "2", + d: "M15 19l-7-7 7-7", + } + } + }, + left: -500.0, + show_id: "left-scroll", + } + } + // Instant Scroll Right + div { + title: "Scroll to the Right", + Scroll { + style: "position: fixed; top: 40%; right: 2rem; background-color: #F59E0B; color: #FFFFFF; padding: 1rem; border-radius: 50%; cursor: pointer; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.4);", + icon: rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + view_box: "0 0 24 24", + stroke: "currentColor", + style: "width: 24px; height: 24px;", + path { + stroke_linecap: "round", + stroke_linejoin: "round", + stroke_width: "2", + d: "M9 5l7 7-7 7", + } + } + }, + left: 500.0, + } + } + // Delayed Scroll to Top + div { + title: "Delayed Scroll to Top", + Scroll { + style: "position: fixed; bottom: 2rem; left: 6rem; background-color: #6D28D9; color: white; padding: 1rem; border-radius: 50%; cursor: pointer; transition: transform 0.3s ease-in-out;", + delay: 2000, + show_id: "top", + } + } + // Instant Scrolling Visibility After Scroll Threshold + div { + title: "Instant Scrolling Visible After Scrolling 400px", + Scroll { + style: "position: fixed; bottom: 6rem; left: 6rem; background-color: #F43F5E; color: #FFFFFF; padding: 12px; border-radius: 50%; border: 2px solid #BE123C; cursor: pointer; transition: transform 0.3s ease-in-out;", + threshold: 400.0, + behavior: Behavior::Instant, + } + } + } + } + } +} diff --git a/examples/yew/dist b/examples/yew/dist new file mode 160000 index 0000000..6db54fe --- /dev/null +++ b/examples/yew/dist @@ -0,0 +1 @@ +Subproject commit 6db54fe74adda02650ed82c59c731f629c0e1695 diff --git a/examples/yew/src/pages/home.rs b/examples/yew/src/pages/home.rs index d49d3dd..a7d6457 100644 --- a/examples/yew/src/pages/home.rs +++ b/examples/yew/src/pages/home.rs @@ -25,7 +25,7 @@ pub fn home() -> Html {
@@ -37,7 +37,7 @@ pub fn home() -> Html {
@@ -50,7 +50,7 @@ pub fn home() -> Html {
diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..711f6c1 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,10 @@ +#[derive(Clone, PartialEq)] +pub enum Behavior { + Auto, + Instant, + Smooth, +} + +/// Default CSS style for the scroll-to-top button. +pub const SCROLL_TO_TOP_STYLE: &'static str = + "position: fixed; bottom: 1rem; right: 1rem; background-color: #3b82f6; color: #ffffff; padding: 0.75rem; border-radius: 50%; cursor: pointer; transition: background-color 300ms ease-in-out;"; diff --git a/src/dioxus.rs b/src/dioxus.rs new file mode 100644 index 0000000..c6d0325 --- /dev/null +++ b/src/dioxus.rs @@ -0,0 +1,283 @@ +use crate::common::{Behavior, SCROLL_TO_TOP_STYLE}; +use dioxus::prelude::*; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; +use web_sys::{window, ScrollBehavior, ScrollToOptions}; + +/// Properties for configuring the `Scroll` component. +/// +/// This component provides a scroll-to-top button with customizable styles, behavior, +/// and functionality. It supports scrolling to specific positions, managing visibility +/// based on scroll position, and triggering callbacks for scroll events. +#[derive(Props, Clone, PartialEq)] +pub struct ScrollProps { + /// Custom inline styles for the scroll-to-top button. + /// + /// Accepts a `&'static str` to define CSS properties for the button. + /// Defaults to the built-in `SCROLL_TO_TOP_STYLE`. + #[props(default = SCROLL_TO_TOP_STYLE)] + pub style: &'static str, + + /// Custom CSS classes for the scroll-to-top button. + /// + /// Accepts a `&'static str` for additional styling using class selectors. + /// Defaults to an empty string. + #[props(default = "")] + pub class: &'static str, + + /// Custom icon for the scroll button. + /// + /// This can be an SVG, HTML, or any valid Dioxus `Element` type to define the + /// button's visual representation. Defaults to an internal SVG icon. + #[props(default = default_svg())] + pub icon: Element, + + /// Behavior of the scroll action. + /// + /// Defines how the scroll will occur (e.g., `smooth` or `instant`). + /// Defaults to `Behavior::Smooth`. + #[props(default = Behavior::Smooth)] + pub behavior: Behavior, + + /// Vertical scroll target position in pixels. + /// + /// Specifies the top position to scroll to. Defaults to `0.0`. + #[props(default = 0.0)] + pub top: f64, + + /// Horizontal scroll target position in pixels. + /// + /// Specifies the left position to scroll to (for horizontal scrolling). + /// Defaults to `0.0`. + #[props(default = 0.0)] + pub left: f64, + + /// Additional offset in pixels for the scroll target. + /// + /// Useful for adjusting the target position to account for fixed headers + /// or other elements. Defaults to `0.0`. + #[props(default = 0.0)] + pub offset: f64, + + /// Delay before initiating the scroll action, in milliseconds. + /// + /// This allows a pause before the scrolling begins. Defaults to `0`. + #[props(default = 0)] + pub delay: u32, + + /// Enable or disable automatic visibility based on scroll position. + /// + /// When `true`, the scroll button will automatically appear or hide + /// based on the user's current scroll position. Defaults to `true`. + #[props(default = true)] + pub auto_hide: bool, + + /// Scroll threshold in pixels for button visibility. + /// + /// Defines the vertical scroll position after which the scroll-to-top + /// button becomes visible. Defaults to `20.0`. + #[props(default = 20.0)] + pub threshold: f64, + + /// Callback triggered when scrolling begins. + /// + /// Use this to handle actions like logging, animations, or UI updates + /// when the scrolling starts. Defaults to no-op. + #[props(default = Callback::default())] + pub on_begin: Callback<(), ()>, + + /// Callback triggered when scrolling ends. + /// + /// Use this to handle actions like resetting states, analytics, or + /// displaying notifications when the scrolling completes. Defaults to no-op. + #[props(default = Callback::default())] + pub on_end: Callback<(), ()>, + + /// Update the URL hash during scrolling. + /// + /// When `true`, the browser's URL hash will be updated to reflect the + /// scroll target. Defaults to `true`. + #[props(default = true)] + pub update_hash: bool, + + /// Target container ID for displaying the scroll button. + /// + /// When specified, the button will only be displayed if the scroll + /// position of the container with the given ID equals to the current scroll y position. + /// Defaults to an empty string. + #[props(default = "")] + pub show_id: &'static str, + + /// Target container ID for scrolling actions. + /// + /// When specified, the scrolling will be applied to the container with + /// the given ID, instead of the default scrolling context (e.g. Scrolling to the top). Defaults to an empty string. + #[props(default = "")] + pub scroll_id: &'static str, +} + +#[component] +pub fn Scroll(props: ScrollProps) -> Element { + let mut is_visible = use_signal(|| false); + + let container_element: Option = if props.show_id.is_empty() { + None + } else { + window() + .expect("window not available") + .document() + .unwrap() + .get_element_by_id(props.show_id) + }; + + use_effect(move || { + let container_element = container_element.clone(); + let threshold = props.threshold; + let auto_hide = props.auto_hide; + + if auto_hide { + let closure = Closure::new({ + move || { + let window = window().expect("window not available"); + let scroll_position = window.scroll_y().unwrap_or(0.0); + + if let Some(container) = container_element.as_ref() { + let container_position = container.get_bounding_client_rect().top(); + is_visible.set(scroll_position > container_position); + } else { + is_visible.set(scroll_position > threshold); + } + } + }); + let window = window().expect("window not available"); + + window + .add_event_listener_with_callback("scroll", closure.as_ref().unchecked_ref()) + .expect("Failed to add scroll event listener"); + + closure.forget(); + } + + (move || { + if auto_hide { + let window = window().expect("window not available"); + let closure = Closure::wrap(Box::new(|| {}) as Box); + window + .remove_event_listener_with_callback("scroll", closure.as_ref().unchecked_ref()) + .expect("Failed to remove scroll event listener"); + } + })() + }); + + let on_click = { + move |_| { + if props.delay > 0 { + let behavior = props.behavior.clone(); + let top = props.top; + let left = props.left; + let offset = props.offset; + let update_hash = props.update_hash; + let scroll_id = props.scroll_id.to_string(); + let on_begin = props.on_begin.clone(); + let on_end = props.on_end.clone(); + gloo::timers::callback::Timeout::new(props.delay, move || { + on_begin.call(()); + scroll_to( + top, + left, + offset, + behavior, + update_hash, + Some(scroll_id.clone()), + ); + on_end.call(()); + }) + .forget(); + } else { + props.on_begin.call(()); + scroll_to( + props.top, + props.left, + props.offset, + props.behavior.clone(), + props.update_hash, + Some(props.scroll_id.to_string()), + ); + props.on_end.call(()); + } + } + }; + + rsx! { + if is_visible() { + div { + class: props.class, + style: props.style, + onclick: on_click, + {props.icon} + } + } + } +} + +/// Helper function to scroll to a specific position +fn scroll_to( + top: f64, + left: f64, + offset: f64, + behavior: Behavior, + update_hash: bool, + scroll_id: Option, +) { + let options = ScrollToOptions::new(); + options.set_left(left); + let window = window().expect("window not available"); + + match behavior { + Behavior::Auto => options.set_behavior(ScrollBehavior::Auto), + Behavior::Instant => options.set_behavior(ScrollBehavior::Instant), + Behavior::Smooth => options.set_behavior(ScrollBehavior::Smooth), + } + + if let Some(container) = scroll_id + .clone() + .and_then(|id| window.document().unwrap().get_element_by_id(&id)) + { + let container_position = container.get_bounding_client_rect().top(); + options.set_top(container_position); + } else { + options.set_top(top + offset); + } + + window.scroll_with_scroll_to_options(&options); + + if update_hash { + if let Some(hash) = scroll_id { + let hash = format!("#{}", hash); + let _ = window + .history() + .unwrap() + .push_state_with_url(&JsValue::NULL, "", Some(&hash)); + } + } +} + +/// Default SVG content +fn default_svg() -> Element { + rsx! { + svg { + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + view_box: "0 0 24 24", + stroke: "currentColor", + style: "width: 16px; height: 16px;", + path { + stroke_linecap: "round", + stroke_linejoin: "round", + stroke_width: "2", + d: "M5 10l7-7m0 0l7 7m-7-7v18", + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 370fc4c..c40f0e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,5 +5,12 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] +pub mod common; + #[cfg(feature = "yew")] pub mod yew; + +#[cfg(feature = "dio")] +pub mod dioxus; + +pub use common::Behavior; diff --git a/src/yew.rs b/src/yew.rs index a5d4f36..05c79f0 100644 --- a/src/yew.rs +++ b/src/yew.rs @@ -1,20 +1,10 @@ +use crate::common::{Behavior, SCROLL_TO_TOP_STYLE}; use gloo::events::EventListener; use gloo::utils::window; use wasm_bindgen::JsValue; use web_sys::{Element, ScrollBehavior, ScrollToOptions}; use yew::prelude::*; -#[derive(Clone, PartialEq)] -pub enum Behavior { - Auto, - Instant, - Smooth, -} - -/// Default CSS style for the scroll-to-top button. -const SCROLL_TO_TOP_STYLE: &'static str = - "position: fixed; bottom: 1rem; right: 1rem; background-color: #3b82f6; color: #ffffff; padding: 0.75rem; border-radius: 50%; cursor: pointer; transition: background-color 300ms ease-in-out;"; - /// Properties for configuring the `Scroll` component. /// /// This component provides a scroll-to-top button with customizable styles, behavior, @@ -36,12 +26,12 @@ pub struct ScrollProps { #[prop_or_default] pub class: &'static str, - /// Custom content for the scroll button. + /// Custom icon for the scroll button. /// /// This can be an SVG, HTML, or any valid Yew `Html` type to define the /// button's visual representation. Defaults to an internal SVG icon. #[prop_or_else(default_svg)] - pub content: Html, + pub icon: Html, /// Behavior of the scroll action. /// @@ -183,7 +173,8 @@ pub struct ScrollProps { /// /// ## Custom Content and Style /// ```rust -/// use scroll_rs::yew::{Scroll, Behavior}; +/// use scroll_rs::yew::Scroll; +/// use scroll_rs::Behavior; /// use yew::prelude::*; /// /// #[function_component(CustomScrollButton)] @@ -191,7 +182,7 @@ pub struct ScrollProps { /// html! { /// /// /// @@ -220,7 +211,7 @@ pub struct ScrollProps { /// {"Scroll to Section 1"} /// }} /// /> @@ -316,7 +307,7 @@ pub fn scroll(props: &ScrollProps) -> Html { html! { if is_visible {
- { props.content.clone() } + { props.icon.clone() }
} } @@ -366,7 +357,7 @@ fn scroll_to( } } -/// Default SVG content for the scroll button. +/// Default SVG icon for the scroll button. fn default_svg() -> Html { html! {