active_class prop for Router

This commit is contained in:
Maccesch 2022-03-13 16:35:28 +00:00
parent 1080ffe52d
commit 9a23ee4612
6 changed files with 52 additions and 8 deletions

3
.gitignore vendored
View file

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

View file

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

View file

@ -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 {

View file

@ -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();

View file

@ -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 {} }

View file

@ -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.