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}