mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-12-18 00:23:07 +00:00
Merge pull request #309 from Synphonyte/master
active_class prop for Router
This commit is contained in:
commit
71c96a8053
7 changed files with 62 additions and 15 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@ Cargo.lock
|
|||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
tarpaulin-report.html
|
||||
.idea
|
|
@ -1,4 +1,5 @@
|
|||
#[derive(Default)]
|
||||
pub struct RouterCfg {
|
||||
pub base_url: Option<String>,
|
||||
pub active_class: Option<String>,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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 {} }
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue