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/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
tarpaulin-report.html
|
tarpaulin-report.html
|
||||||
|
.idea
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
let active_class = if active { active_class_name } else { "".into() };
|
||||||
.then(|| active_class.unwrap_or("active"))
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
a {
|
a {
|
||||||
|
|
|
@ -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,
|
||||||
let svc = RouterCore::new(tx, RouterCfg { base_url });
|
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();
|
||||||
|
|
|
@ -12,9 +12,7 @@ pub fn use_route(cx: &ScopeState) -> &UseRoute {
|
||||||
.consume_context::<RouterService>()
|
.consume_context::<RouterService>()
|
||||||
.expect("Cannot call use_route outside the scope of a Router component");
|
.expect("Cannot call use_route outside the scope of a Router component");
|
||||||
|
|
||||||
let route_context = cx
|
let route_context = cx.consume_context::<RouteContext>();
|
||||||
.consume_context::<RouteContext>()
|
|
||||||
.expect("Cannot call use_route outside the scope of a Router component");
|
|
||||||
|
|
||||||
router.subscribe_onchange(cx.scope_id());
|
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.
|
/// A handle to the current location of the router.
|
||||||
pub struct UseRoute {
|
pub struct UseRoute {
|
||||||
pub(crate) route: Arc<ParsedRoute>,
|
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 {
|
impl UseRoute {
|
||||||
|
@ -84,9 +84,12 @@ impl UseRoute {
|
||||||
/// `value.parse::<T>()`. This method returns `None` if the named
|
/// `value.parse::<T>()`. This method returns `None` if the named
|
||||||
/// parameter does not exist in the current path.
|
/// parameter does not exist in the current path.
|
||||||
pub fn segment(&self, name: &str) -> Option<&str> {
|
pub fn segment(&self, name: &str) -> Option<&str> {
|
||||||
let index = self
|
let total_route = match self.route_context {
|
||||||
.route_context
|
None => self.route.url.path(),
|
||||||
.total_route
|
Some(ref ctx) => &ctx.total_route,
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = total_route
|
||||||
.trim_start_matches('/')
|
.trim_start_matches('/')
|
||||||
.split('/')
|
.split('/')
|
||||||
.position(|segment| segment.starts_with(':') && &segment[1..] == name)?;
|
.position(|segment| segment.starts_with(':') && &segment[1..] == name)?;
|
||||||
|
|
|
@ -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 {} }
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue