Răsfoiți Sursa

Merge pull request #309 from Synphonyte/master

active_class prop for Router
Jon Kelley 3 ani în urmă
părinte
comite
71c96a8053

+ 2 - 1
.gitignore

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

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

@@ -1,4 +1,5 @@
 #[derive(Default)]
 pub struct RouterCfg {
     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.
     ///
-    /// 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)]
     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 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 url = route.url();
     let path = url.path();
     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! {
         a {

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

@@ -28,6 +28,13 @@ pub struct RouterProps<'a> {
     /// This lets you easily implement redirects
     #[props(default)]
     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.
@@ -40,9 +47,13 @@ pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
     let svc = cx.use_hook(|_| {
         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({
             let svc = svc.clone();

+ 10 - 7
packages/router/src/hooks/use_route.rs

@@ -12,9 +12,7 @@ pub fn use_route(cx: &ScopeState) -> &UseRoute {
             .consume_context::<RouterService>()
             .expect("Cannot call use_route outside the scope of a Router component");
 
-        let route_context = cx
-            .consume_context::<RouteContext>()
-            .expect("Cannot call use_route outside the scope of a Router component");
+        let route_context = cx.consume_context::<RouteContext>();
 
         router.subscribe_onchange(cx.scope_id());
 
@@ -36,7 +34,9 @@ pub fn use_route(cx: &ScopeState) -> &UseRoute {
 /// A handle to the current location of the router.
 pub struct UseRoute {
     pub(crate) route: Arc<ParsedRoute>,
-    pub(crate) route_context: RouteContext,
+
+    /// If `use_route` is used inside a `Route` component this has some context otherwise `None`.
+    pub(crate) route_context: Option<RouteContext>,
 }
 
 impl UseRoute {
@@ -84,9 +84,12 @@ impl UseRoute {
     /// `value.parse::<T>()`. This method returns `None` if the named
     /// parameter does not exist in the current path.
     pub fn segment(&self, name: &str) -> Option<&str> {
-        let index = self
-            .route_context
-            .total_route
+        let total_route = match self.route_context {
+            None => self.route.url.path(),
+            Some(ref ctx) => &ctx.total_route,
+        };
+
+        let index = total_route
             .trim_start_matches('/')
             .split('/')
             .position(|segment| segment.starts_with(':') && &segment[1..] == name)?;

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

@@ -22,6 +22,7 @@ fn simple_test() {
         cx.render(rsx! {
             Router {
                 onchange: move |route: RouterService| log::info!("route changed to {:?}", route.current_location()),
+                active_class: "is-active",
                 Route { to: "/", Home {} }
                 Route { to: "blog"
                     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
 
 Each route in your app is comprised of segments and queries. Segments are the portions of the route delimited by forward slashes.