Skip to main content

tablo_core/
navigation.rs

1//! Sidebar navigation: [`NavigationItem`] and the [`NavTarget`] it points with.
2
3use topcoat::{context::Cx, icon::IconData, view::Attributes};
4
5/// The `href` and runtime-navigation attributes of a link to another panel page.
6pub(crate) fn runtime_link(cx: &Cx, href: &str) -> Attributes {
7    topcoat::runtime::link_attrs(cx, href.to_string(), topcoat::runtime::prefetch_mode(cx))
8}
9
10/// Where a sidebar entry points.
11#[derive(Clone, Default, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum NavTarget {
14    /// No URL yet; the owning Panel resolves it.
15    #[default]
16    Derived,
17    /// An explicit URL.
18    Url(String),
19}
20
21impl NavTarget {
22    /// The URL this target names.
23    pub fn url(&self) -> Option<&str> {
24        match self {
25            Self::Derived => None,
26            Self::Url(url) => Some(url),
27        }
28    }
29}
30
31impl std::fmt::Debug for NavTarget {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Self::Derived => f.write_str("Derived"),
35            Self::Url(url) => f.debug_tuple("Url").field(url).finish(),
36        }
37    }
38}
39
40/// A sidebar entry: a resource's, from its [`ResourceDef`](super::ResourceDef), or a `Page`'s.
41#[derive(Clone, Debug, Default)]
42pub struct NavigationItem {
43    pub label: String,
44    /// Where this entry points.
45    pub target: NavTarget,
46    /// Sort key for the sidebar.
47    pub order: i32,
48    /// The icon before the label, if any.
49    pub icon: Option<IconData>,
50}
51
52impl NavigationItem {
53    /// A sidebar entry at an explicit `url`.
54    pub fn at(label: impl Into<String>, url: impl Into<String>) -> Self {
55        Self {
56            label: label.into(),
57            target: NavTarget::Url(url.into()),
58            order: 0,
59            icon: None,
60        }
61    }
62
63    /// This entry with `icon` before its label.
64    #[must_use]
65    pub fn icon(mut self, icon: IconData) -> Self {
66        self.icon = Some(icon);
67        self
68    }
69
70    /// Resolve a [`NavTarget::Derived`] entry to `url`.
71    pub(crate) fn resolved(mut self, url: &str) -> Self {
72        if matches!(self.target, NavTarget::Derived) {
73            self.target = NavTarget::Url(url.to_string());
74        }
75        self
76    }
77
78    /// The URL this entry points at.
79    pub fn url(&self) -> Option<&str> {
80        self.target.url()
81    }
82
83    /// Whether this item is current for the given request path.
84    pub fn is_current_path(&self, current_path: &str) -> bool {
85        let Some(url) = self.url() else {
86            return false;
87        };
88        if current_path == url {
89            return true;
90        }
91        current_path
92            .strip_prefix(url)
93            .is_some_and(|rest| rest.starts_with('/'))
94    }
95}
96
97#[cfg(test)]
98mod tests;