From 8d52a6d208fa7540c443e1610565acb70fb58153 Mon Sep 17 00:00:00 2001 From: Adrian Wannenmacher Date: Tue, 13 Dec 2022 12:18:54 +0100 Subject: [PATCH] document components --- .../router/src/components/history_buttons.rs | 86 ++++++++++++++- packages/router/src/components/link.rs | 103 ++++++++++++++---- packages/router/src/components/outlet.rs | 56 +++++++++- packages/router/src/lib.rs | 2 + 4 files changed, 223 insertions(+), 24 deletions(-) diff --git a/packages/router/src/components/history_buttons.rs b/packages/router/src/components/history_buttons.rs index 6f7109178..979ecd2b1 100644 --- a/packages/router/src/components/history_buttons.rs +++ b/packages/router/src/components/history_buttons.rs @@ -4,11 +4,53 @@ use log::error; use crate::utils::use_router_internal::use_router_internal; +/// The properties for a [`GoBackButton`] or a [`GoForwardButton`]. #[derive(Debug, Props)] pub struct HistoryButtonProps<'a> { + /// The children to render within the generated HTML button tag. pub children: Element<'a>, } +/// A button to go back through the navigation history. Similar to a browsers back button. +/// +/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// +/// The button will disable itself if it is known that no prior history is available. +/// +/// [`use_router`]: crate::hooks::use_router +/// +/// # Panic +/// - When the [`GoBackButton`] is not nested within another component calling the [`use_router`] +/// hook, but only in debug builds. +/// +/// # Example +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::prelude::*; +/// fn App(cx: Scope) -> Element { +/// use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// ..Default::default() +/// }, +/// &|| Segment::empty() +/// ); +/// +/// render! { +/// GoBackButton { +/// "go back" +/// } +/// } +/// } +/// # +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// # assert_eq!( +/// # dioxus_ssr::render(&vdom), +/// # r#""# +/// # ); +/// ``` #[allow(non_snake_case)] pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { let HistoryButtonProps { children } = cx.props; @@ -16,12 +58,12 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { // hook up to router let router = match use_router_internal(&cx) { Some(r) => r, + #[allow(unreachable_code)] None => { let msg = "`GoBackButton` must have access to a parent router"; error!("{msg}, will be inactive"); #[cfg(debug_assertions)] panic!("{}", msg); - #[cfg(not(debug_assertions))] anyhow::bail!("{msg}"); } }; @@ -44,6 +86,46 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { } } +/// A button to go forward through the navigation history. Similar to a browsers forward button. +/// +/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// +/// The button will disable itself if it is known that no later history is available. +/// +/// [`use_router`]: crate::hooks::use_router +/// +/// # Panic +/// - When the [`GoForwardButton`] is not nested within another component calling the [`use_router`] +/// hook, but only in debug builds. +/// +/// # Example +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::prelude::*; +/// fn App(cx: Scope) -> Element { +/// use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// ..Default::default() +/// }, +/// &|| Segment::empty() +/// ); +/// +/// render! { +/// GoForwardButton { +/// "go forward" +/// } +/// } +/// } +/// # +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// # assert_eq!( +/// # dioxus_ssr::render(&vdom), +/// # r#""# +/// # ); +/// ``` #[allow(non_snake_case)] pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { let HistoryButtonProps { children } = cx.props; @@ -51,12 +133,12 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { // hook up to router let router = match use_router_internal(&cx) { Some(r) => r, + #[allow(unreachable_code)] None => { let msg = "`GoForwardButton` must have access to a parent router"; error!("{msg}, will be inactive"); #[cfg(debug_assertions)] panic!("{}", msg); - #[cfg(not(debug_assertions))] anyhow::bail!("{msg}"); } }; diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index f9940c86b..c146855c5 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -7,36 +7,99 @@ use crate::utils::use_router_internal::use_router_internal; /// The properties for a [`Link`]. #[derive(Debug, Props)] pub struct LinkProps<'a> { - /// A class to apply to the generated HTML anchor when the `target` route is active. - /// - /// This overrides the `active_class` property of a [`Router`]. - /// - /// [`Router`]: crate::components::Router + /// A class to apply to the generate HTML anchor tag if the `target` route is active. pub active_class: Option<&'a str>, - /// The children to render within the generated HTML anchor. + /// The children to render within the generated HTML anchor tag. pub children: Element<'a>, - /// The `class` attribute of the generated HTML anchor. + /// The class attribute for the generated HTML anchor tag. /// - /// When the `target` route is active, `active_class` is appended at the end. + /// If `active_class` is [`Some`] and the `target` route is active, `active_class` will be + /// appended at the end of `class`. pub class: Option<&'a str>, - /// Require the _exact_ target route to be active, for the link to be active. See - /// [`RouterState::is_active`](crate::state::RouterState::is_active). + /// Require the __exact__ `target` to be active, for the [`Link`] to be active. + /// + /// See [`RouterState::is_at`](dioxus_router_core::RouterState::is_at) for more details. #[props(default)] pub exact: bool, - /// The `id` attribute of the generated HTML anchor. + /// The id attribute for the generated HTML anchor tag. pub id: Option<&'a str>, - /// When [`true`], the `target` will be opened in a new tab. + /// When [`true`], the `target` route will be opened in a new tab. + /// + /// This does not change whether the [`Link`] is active or not. #[props(default)] pub new_tab: bool, - /// The `rel` attribute of the generated HTML anchor. + /// The rel attribute for the generated HTML anchor tag. /// - /// Defaults to `"noreferrer noopener"` for [`ExternalTarget`] targets. + /// For external `target`s, this defaults to `noopener noreferrer`. pub rel: Option<&'a str>, - /// The navigation target. Corresponds to the `href` of an HTML anchor. + /// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag. #[props(into)] pub target: NavigationTarget, } +/// A link to navigate to another route. +/// +/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// +/// Unlike a regular HTML anchor, a [`Link`] allows the router to handle the navigation and doesn't +/// cause the browser to load a new page. +/// +/// However, in the background a [`Link`] still generates an anchor, which you can use for styling +/// as normal. +/// +/// [`use_router`]: crate::hooks::use_router +/// +/// # External targets +/// When the [`Link`]s target is an [`External`] target, that is used as the `href` directly. This +/// means that a [`Link`] can always navigate to an [`External`] target. +/// +/// This is different from a [`Navigator`], which can only navigate to external targets when the +/// routers [`HistoryProvider`] supports it. +/// +/// [`External`]: dioxus_router_core::navigation::NavigationTarget::External +/// [`HistoryProvider`]: dioxus_router_core::history::HistoryProvider +/// [`Navigator`]: dioxus_router_core::Navigator +/// +/// # Panic +/// - When the [`Link`] is not nested within another component calling the [`use_router`] hook, but +/// only in debug builds. +/// +/// # Example +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::prelude::*; +/// fn App(cx: Scope) -> Element { +/// use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// ..Default::default() +/// }, +/// &|| Segment::empty() +/// ); +/// +/// render! { +/// Link { +/// active_class: "active", +/// class: "link_class", +/// exact: true, +/// id: "link_id", +/// new_tab: true, +/// rel: "link_rel", +/// target: "/", +/// +/// "A fully configured link" +/// } +/// } +/// } +/// # +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// # assert_eq!( +/// # dioxus_ssr::render(&vdom), +/// # r#"A fully configured link"# +/// # ); +/// ``` #[allow(non_snake_case)] pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { let LinkProps { @@ -53,12 +116,12 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { // hook up to router let router = match use_router_internal(&cx) { Some(r) => r, + #[allow(unreachable_code)] None => { let msg = "`Link` must have access to a parent router"; error!("{msg}, will be inactive"); #[cfg(debug_assertions)] panic!("{}", msg); - #[cfg(not(debug_assertions))] anyhow::bail!("{msg}"); } }; @@ -85,11 +148,9 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { let is_external = matches!(target, NavigationTarget::External(_)); let is_router_nav = !is_external && !new_tab; let prevent_default = is_router_nav.then_some("onclick").unwrap_or_default(); - let rel = rel.unwrap_or( - is_external - .then_some("noopener noreferrer") - .unwrap_or_default(), - ); + let rel = rel + .or_else(|| is_external.then_some("noopener noreferrer")) + .unwrap_or_default(); render! { a { diff --git a/packages/router/src/components/outlet.rs b/packages/router/src/components/outlet.rs index 35c57cae4..bddb11c46 100644 --- a/packages/router/src/components/outlet.rs +++ b/packages/router/src/components/outlet.rs @@ -4,12 +4,66 @@ use log::error; use crate::utils::use_router_internal::use_router_internal; +/// The properties for an [`Outlet`]. #[derive(Debug, Eq, PartialEq, Props)] pub struct OutletProps { + /// Override the [`Outlet`]s nesting depth. + /// + /// By default the [`Outlet`] will find its own depth. This property overrides that depth. + /// Nested [`Outlet`]s will respect this override and calculate their depth based on it. pub depth: Option, + /// The content name. + /// + /// By default, the outlet will render unnamed content. If this is set to a name, the outlet + /// will render content for that name, defined via [`RouteContent::MultiContent`]. + /// + /// [`RouteContent::MultiContent`]: dioxus_router_core::routes::RouteContent::MultiContent pub name: Option, } +/// An outlet for the current content. +/// +/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// +/// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content +/// of the active route that is __exactly as deep__. +/// +/// [`use_router`]: crate::hooks::use_router +/// +/// # Panic +/// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook, +/// but only in debug builds. +/// +/// # Example +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::prelude::*; +/// fn App(cx: Scope) -> Element { +/// use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// ..Default::default() +/// }, +/// &|| Segment::content(comp(Content)) +/// ); +/// +/// render! { +/// h1 { "App" } +/// Outlet { } // The content component will be rendered here +/// } +/// } +/// +/// fn Content(cx: Scope) -> Element { +/// render! { +/// p { "Content" } +/// } +/// } +/// # +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Content

"); +/// ``` #[allow(non_snake_case)] pub fn Outlet(cx: Scope) -> Element { let OutletProps { depth, name } = cx.props; @@ -17,12 +71,12 @@ pub fn Outlet(cx: Scope) -> Element { // hook up to router let router = match use_router_internal(&cx) { Some(r) => r, + #[allow(unreachable_code)] None => { let msg = "`Outlet` must have access to a parent router"; error!("{msg}, will be inactive"); #[cfg(debug_assertions)] panic!("{}", msg); - #[cfg(not(debug_assertions))] anyhow::bail!("{msg}"); } }; diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 50f51705f..3164e0a15 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -1,3 +1,5 @@ +/// Components interacting with the router. +#[deny(missing_docs)] pub mod components { pub(crate) mod default_errors;