dioxus_docs_kit/components/
sidebar.rs1use dioxus::prelude::*;
2use dioxus_mdx::HttpMethod;
3
4use crate::DocsContext;
5use crate::components::docs_layout::ActiveTab;
6use crate::registry::{DocsRegistry, NavGroup};
7
8#[component]
10pub fn DocsSidebar() -> Element {
11 let registry = use_context::<&'static DocsRegistry>();
12 let ActiveTab(active_tab) = use_context::<ActiveTab>();
13 let nav = ®istry.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#[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 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#[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#[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}