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