Skip to main content

dioxus_docs_kit/components/
sidebar.rs

1use dioxus::prelude::*;
2use dioxus_mdx::HttpMethod;
3
4use crate::DocsContext;
5use crate::registry::{DocsRegistry, NavGroup};
6
7/// Documentation sidebar navigation.
8#[component]
9pub fn DocsSidebar() -> Element {
10    let registry = use_context::<&'static DocsRegistry>();
11    let active_tab = use_context::<Signal<String>>();
12    let nav = &registry.nav;
13
14    let groups: Vec<&NavGroup> = if nav.has_tabs() {
15        nav.groups_for_tab(&active_tab())
16    } else {
17        nav.groups.iter().collect()
18    };
19
20    rsx! {
21        nav { class: "space-y-6",
22            for group in groups.iter() {
23                SidebarGroup { group: (*group).clone() }
24            }
25        }
26    }
27}
28
29/// A single sidebar group (normal or API Reference).
30#[component]
31fn SidebarGroup(group: NavGroup) -> Element {
32    let registry = use_context::<&'static DocsRegistry>();
33    let api_entries = registry.get_api_sidebar_entries();
34    let is_api_group = group.group == registry.api_group_name;
35
36    if is_api_group {
37        rsx! {
38            div { class: "space-y-2",
39                h3 { class: "font-semibold text-sm text-base-content/70 uppercase tracking-wider px-3",
40                    "{group.group}"
41                }
42                ul { class: "space-y-1",
43                    for page in group.pages.iter() {
44                        SidebarLink { path: page.clone() }
45                    }
46                }
47                // Dynamic API endpoints grouped by tag
48                for (tag, entries) in api_entries.iter() {
49                    div { class: "mt-3",
50                        h4 { class: "text-xs font-medium text-base-content/50 uppercase tracking-wider px-3 mb-1",
51                            "{tag.name}"
52                        }
53                        ul { class: "space-y-0.5",
54                            for entry in entries.iter() {
55                                ApiSidebarLink {
56                                    slug: entry.slug.clone(),
57                                    title: entry.title.clone(),
58                                    method: entry.method,
59                                }
60                            }
61                        }
62                    }
63                }
64            }
65        }
66    } else {
67        rsx! {
68            div { class: "space-y-2",
69                h3 { class: "font-semibold text-sm text-base-content/70 uppercase tracking-wider px-3",
70                    "{group.group}"
71                }
72                ul { class: "space-y-1",
73                    for page in group.pages.iter() {
74                        SidebarLink { path: page.clone() }
75                    }
76                }
77            }
78        }
79    }
80}
81
82/// Sidebar link for API endpoints with method badges.
83#[component]
84fn ApiSidebarLink(slug: String, title: String, method: HttpMethod) -> Element {
85    let ctx = use_context::<DocsContext>();
86    let registry = use_context::<&'static DocsRegistry>();
87
88    let prefix = registry.get_first_api_prefix().unwrap_or("api-reference");
89    let path = format!("{prefix}/{slug}");
90
91    let is_active = (ctx.current_path)() == path;
92
93    let active_class = if is_active {
94        "bg-primary/10 text-primary font-medium border-l-2 border-primary"
95    } else {
96        "text-base-content/70 hover:text-base-content hover:bg-base-200"
97    };
98
99    let badge_class = method.badge_class();
100
101    let method_label = match method {
102        HttpMethod::Delete => "DEL",
103        _ => method.as_str(),
104    };
105
106    let href = format!("{}/{}", ctx.base_path, path);
107
108    rsx! {
109        li {
110            Link {
111                to: NavigationTarget::Internal(href),
112                class: "flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg transition-colors {active_class}",
113                span { class: "badge badge-xs font-mono font-bold {badge_class} shrink-0",
114                    "{method_label}"
115                }
116                span { class: "truncate", "{title}" }
117            }
118        }
119    }
120}
121
122/// Individual sidebar link.
123#[component]
124fn SidebarLink(path: String) -> Element {
125    let ctx = use_context::<DocsContext>();
126    let registry = use_context::<&'static DocsRegistry>();
127
128    let title = registry.get_sidebar_title(&path).unwrap_or_else(|| {
129        path.split('/')
130            .next_back()
131            .unwrap_or(&path)
132            .replace('-', " ")
133    });
134
135    let current = (ctx.current_path)();
136    let is_active = current == path || (current.is_empty() && path == registry.default_path);
137
138    let active_class = if is_active {
139        "bg-primary/10 text-primary font-medium border-l-2 border-primary"
140    } else {
141        "text-base-content/70 hover:text-base-content hover:bg-base-200"
142    };
143
144    let href = format!("{}/{}", ctx.base_path, path);
145
146    rsx! {
147        li {
148            Link {
149                to: NavigationTarget::Internal(href),
150                class: "block px-3 py-2 text-sm rounded-lg transition-colors {active_class}",
151                "{title}"
152            }
153        }
154    }
155}