dioxus_retrouter/components/link.rs
1use crate::{use_route, RouterContext};
2use dioxus::prelude::*;
3
4/// Props for the [`Link`](struct.Link.html) component.
5#[derive(Props)]
6pub struct LinkProps<'a> {
7 /// The route to link to. This can be a relative path, or a full URL.
8 ///
9 /// ```rust, ignore
10 /// // Absolute path
11 /// Link { to: "/home", "Go Home" }
12 ///
13 /// // Relative path
14 /// Link { to: "../", "Go Up" }
15 /// ```
16 pub to: &'a str,
17
18 /// Set the class of the inner link ['a'](https://www.w3schools.com/tags/tag_a.asp) element.
19 ///
20 /// This can be useful when styling the inner link element.
21 #[props(default, strip_option)]
22 pub class: Option<&'a str>,
23
24 /// Set the class added to the inner link when the current route is the same as the "to" route.
25 ///
26 /// To set all of the active classes inside a Router at the same time use the `active_class`
27 /// prop on the Router component. If both the Router prop as well as this prop are provided then
28 /// this one has precedence. By default set to `"active"`.
29 #[props(default, strip_option)]
30 pub active_class: Option<&'a str>,
31
32 /// Set the ID of the inner link ['a'](https://www.w3schools.com/tags/tag_a.asp) element.
33 ///
34 /// This can be useful when styling the inner link element.
35 #[props(default, strip_option)]
36 pub id: Option<&'a str>,
37
38 /// Set the title of the window after the link is clicked..
39 #[props(default, strip_option)]
40 pub title: Option<&'a str>,
41
42 /// Autodetect if a link is external or not.
43 ///
44 /// This is automatically set to `true` and will use http/https detection
45 #[props(default = true)]
46 pub autodetect: bool,
47
48 /// Is this link an external link?
49 #[props(default = false)]
50 pub external: bool,
51
52 /// New tab?
53 #[props(default = false)]
54 pub new_tab: bool,
55
56 /// Pass children into the `<a>` element
57 pub children: Element<'a>,
58
59 /// The onclick event handler.
60 pub onclick: Option<EventHandler<'a, MouseEvent>>,
61}
62
63/// A component that renders a link to a route.
64///
65/// `Link` components are just [`<a>`](https://www.w3schools.com/tags/tag_a.asp) elements
66/// that link to different pages *within* your single-page app.
67///
68/// If you need to link to a resource outside of your app, then just use a regular
69/// `<a>` element directly.
70///
71/// # Examples
72///
73/// ```rust, ignore
74/// fn Header(cx: Scope) -> Element {
75/// cx.render(rsx!{
76/// Link { to: "/home", "Go Home" }
77/// })
78/// }
79/// ```
80pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
81 let svc = use_context::<RouterContext>(cx);
82
83 let LinkProps {
84 to,
85 class,
86 id,
87 title,
88 autodetect,
89 external,
90 new_tab,
91 children,
92 active_class,
93 ..
94 } = cx.props;
95
96 let is_http = to.starts_with("http") || to.starts_with("https");
97 let outerlink = (*autodetect && is_http) || *external;
98 let prevent_default = if outerlink { "" } else { "onclick" };
99
100 let active_class_name = match active_class {
101 Some(c) => (*c).into(),
102 None => {
103 let active_from_router = match svc {
104 Some(service) => service.cfg.active_class.clone(),
105 None => None,
106 };
107 active_from_router.unwrap_or_else(|| "active".into())
108 }
109 };
110
111 let route = use_route(cx);
112 let url = route.url();
113 let path = url.path();
114 let active = path == cx.props.to;
115 let active_class = if active { active_class_name } else { "".into() };
116
117 cx.render(rsx! {
118 a {
119 href: "{to}",
120 class: format_args!("{} {}", class.unwrap_or(""), active_class),
121 id: format_args!("{}", id.unwrap_or("")),
122 title: format_args!("{}", title.unwrap_or("")),
123 prevent_default: "{prevent_default}",
124 target: format_args!("{}", if * new_tab { "_blank" } else { "" }),
125 onclick: move |evt| {
126 log::trace!("Clicked link to {}", to);
127
128 if !outerlink {
129 if let Some(service) = svc {
130 log::trace!("Pushing route to {}", to);
131 service.push_route(to, cx.props.title.map(|f| f.to_string()), None);
132
133 #[cfg(feature = "web")]
134 {
135 web_sys::window().unwrap().scroll_to_with_x_and_y(0.0, 0.0);
136 }
137 } else {
138 log::error!(
139 "Attempted to create a Link to {} outside of a Router context", cx.props
140 .to,
141 );
142 }
143 }
144
145 if let Some(onclick) = cx.props.onclick.as_ref() {
146 onclick.call(evt);
147 }
148 },
149 children
150 }
151 })
152}