Quellcode durchsuchen

active_class prop for Router

Maccesch vor 3 Jahren
Ursprung
Commit
9a23ee4612

+ 2 - 1
.gitignore

@@ -8,4 +8,5 @@ Cargo.lock
 !.vscode/tasks.json
 !.vscode/tasks.json
 !.vscode/launch.json
 !.vscode/launch.json
 !.vscode/extensions.json
 !.vscode/extensions.json
-tarpaulin-report.html
+tarpaulin-report.html
+.idea

+ 1 - 0
packages/router/src/cfg.rs

@@ -1,4 +1,5 @@
 #[derive(Default)]
 #[derive(Default)]
 pub struct RouterCfg {
 pub struct RouterCfg {
     pub base_url: Option<String>,
     pub base_url: Option<String>,
+    pub active_class: Option<String>,
 }
 }

+ 15 - 4
packages/router/src/components/link.rs

@@ -28,7 +28,9 @@ pub struct LinkProps<'a> {
 
 
     /// Set the class added to the inner link when the current route is the same as the "to" route.
     /// Set the class added to the inner link when the current route is the same as the "to" route.
     ///
     ///
-    /// By default set to `"active"`.
+    /// To set all of the active classes inside a Router at the same time use the `active_class`
+    /// prop on the Router component. If both the Router prop as well as this prop are provided then
+    /// this one has precedence. By default set to `"active"`.
     #[props(default, strip_option)]
     #[props(default, strip_option)]
     pub active_class: Option<&'a str>,
     pub active_class: Option<&'a str>,
 
 
@@ -97,13 +99,22 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
     let outerlink = (*autodetect && is_http) || *external;
     let outerlink = (*autodetect && is_http) || *external;
     let prevent_default = if outerlink { "" } else { "onclick" };
     let prevent_default = if outerlink { "" } else { "onclick" };
 
 
+    let active_class_name = match active_class {
+        Some(c) => (*c).into(),
+        None => {
+            let active_from_router = match svc {
+                Some(service) => service.cfg.active_class.clone(),
+                None => None,
+            };
+            active_from_router.unwrap_or("active".into())
+        }
+    };
+
     let route = use_route(&cx);
     let route = use_route(&cx);
     let url = route.url();
     let url = route.url();
     let path = url.path();
     let path = url.path();
     let active = path == cx.props.to;
     let active = path == cx.props.to;
-    let active_class = active
-        .then(|| active_class.unwrap_or("active"))
-        .unwrap_or("");
+    let active_class = if active { active_class_name } else { "".into() };
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         a {
         a {

+ 14 - 3
packages/router/src/components/router.rs

@@ -28,6 +28,13 @@ pub struct RouterProps<'a> {
     /// This lets you easily implement redirects
     /// This lets you easily implement redirects
     #[props(default)]
     #[props(default)]
     pub onchange: EventHandler<'a, Arc<RouterCore>>,
     pub onchange: EventHandler<'a, Arc<RouterCore>>,
+
+    /// Set the active class of all Link components contained in this router.
+    ///
+    /// This is useful if you don't want to repeat the same `active_class` prop value in every Link.
+    /// By default set to `"active"`.
+    #[props(default, strip_option)]
+    pub active_class: Option<&'a str>,
 }
 }
 
 
 /// A component that conditionally renders children based on the current location of the app.
 /// A component that conditionally renders children based on the current location of the app.
@@ -40,9 +47,13 @@ pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
     let svc = cx.use_hook(|_| {
     let svc = cx.use_hook(|_| {
         let (tx, mut rx) = futures_channel::mpsc::unbounded::<RouteEvent>();
         let (tx, mut rx) = futures_channel::mpsc::unbounded::<RouteEvent>();
 
 
-        let base_url = cx.props.base_url.map(|s| s.to_string());
-
-        let svc = RouterCore::new(tx, RouterCfg { base_url });
+        let svc = RouterCore::new(
+            tx,
+            RouterCfg {
+                base_url: cx.props.base_url.map(|s| s.to_string()),
+                active_class: cx.props.active_class.map(|s| s.to_string()),
+            },
+        );
 
 
         cx.spawn({
         cx.spawn({
             let svc = svc.clone();
             let svc = svc.clone();

+ 1 - 0
packages/router/tests/web_router.rs

@@ -22,6 +22,7 @@ fn simple_test() {
         cx.render(rsx! {
         cx.render(rsx! {
             Router {
             Router {
                 onchange: move |route: RouterService| log::info!("route changed to {:?}", route.current_location()),
                 onchange: move |route: RouterService| log::info!("route changed to {:?}", route.current_location()),
+                active_class: "is-active",
                 Route { to: "/", Home {} }
                 Route { to: "/", Home {} }
                 Route { to: "blog"
                 Route { to: "blog"
                     Route { to: "/", BlogList {} }
                     Route { to: "/", BlogList {} }

+ 19 - 0
packages/router/usage.md

@@ -62,6 +62,25 @@ Link { to: "/blog/welcome",
 }
 }
 ```
 ```
 
 
+#### Active `Links`
+
+When your app has been navigated to a route that matches the route of a `Link`, this `Link` becomes 'active'.
+Active links have a special class attached to them. By default it is simply called `"active"` but it can be
+modified on the `Link` level or on the `Router` level. Both is done through the prop `active_class`.
+If the active class is given on both, the `Router` and the `Link`, the one on the `Link` has precedence.
+
+```rust
+Router {
+    active_class: "custom-active",  // All active links in this router get this class.
+    Link { to: "/", "Home" },
+    Link { 
+        to: "/blog",
+        active_class: "is-active",  // Only for this Link. Overwrites "custom-active" from Router.
+        "Blog" 
+    },
+}
+```
+
 ### Segments
 ### Segments
 
 
 Each route in your app is comprised of segments and queries. Segments are the portions of the route delimited by forward slashes.
 Each route in your app is comprised of segments and queries. Segments are the portions of the route delimited by forward slashes.