Skip to main content

dioxus_docs_kit/components/
sidebar.rs

1use dioxus::prelude::*;
2use dioxus_mdx::HttpMethod;
3
4use crate::DocsContext;
5use crate::components::docs_layout::ActiveTab;
6use crate::registry::{DocsRegistry, NavGroup};
7
8/// Documentation sidebar navigation.
9#[component]
10pub fn DocsSidebar() -> Element {
11    let registry = use_context::<&'static DocsRegistry>();
12    let ActiveTab(active_tab) = use_context::<ActiveTab>();
13    let nav = &registry.nav;
14
15    let groups: Vec<&NavGroup> = if nav.has_tabs() {
16        nav.groups_for_tab(&active_tab())
17    } else {
18        nav.groups.iter().collect()
19    };
20
21    rsx! {
22        nav { class: "dk-nav space-y-6",
23            for group in groups.iter() {
24                SidebarGroup { group: (*group).clone() }
25            }
26        }
27    }
28}
29
30/// A single sidebar group (normal or API Reference).
31#[component]
32fn SidebarGroup(group: NavGroup) -> Element {
33    let registry = use_context::<&'static DocsRegistry>();
34    let api_entries = registry.get_api_sidebar_entries();
35    let is_api_group = group.group == registry.api_group_name;
36
37    if is_api_group {
38        rsx! {
39            div { class: "dk-nav-group space-y-2",
40                h3 { class: "dk-nav-group-title font-semibold text-sm text-base-content/70 uppercase tracking-wider px-3",
41                    "{group.group}"
42                }
43                ul { class: "space-y-1",
44                    for page in group.pages.iter() {
45                        SidebarLink { path: page.clone() }
46                    }
47                }
48                // Dynamic API endpoints grouped by tag
49                for (tag, entries) in api_entries.iter() {
50                    div { class: "dk-nav-subgroup mt-3",
51                        h4 { class: "dk-nav-subgroup-title text-xs font-medium text-base-content/50 uppercase tracking-wider px-3 mb-1",
52                            "{tag.name}"
53                        }
54                        ul { class: "space-y-0.5",
55                            for entry in entries.iter() {
56                                ApiSidebarLink {
57                                    prefix: entry.prefix.clone(),
58                                    slug: entry.slug.clone(),
59                                    title: entry.title.clone(),
60                                    method: entry.method,
61                                }
62                            }
63                        }
64                    }
65                }
66            }
67        }
68    } else {
69        rsx! {
70            div { class: "dk-nav-group space-y-2",
71                h3 { class: "dk-nav-group-title font-semibold text-sm text-base-content/70 uppercase tracking-wider px-3",
72                    "{group.group}"
73                }
74                ul { class: "space-y-1",
75                    for page in group.pages.iter() {
76                        SidebarLink { path: page.clone() }
77                    }
78                }
79            }
80        }
81    }
82}
83
84/// Sidebar link for API endpoints with method badges.
85#[component]
86fn ApiSidebarLink(prefix: String, slug: String, title: String, method: HttpMethod) -> Element {
87    let ctx = use_context::<DocsContext>();
88
89    let path = format!("{prefix}/{slug}");
90
91    let is_active = (ctx.current_path)() == path;
92
93    let active_class = if is_active {
94        "dk-nav-item-active 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: "dk-nav-item dk-nav-item-api 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        "dk-nav-item-active 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: "dk-nav-item block px-3 py-2 text-sm rounded-lg transition-colors {active_class}",
151                "{title}"
152            }
153        }
154    }
155}