use dioxus::prelude::*;
use dioxus_mdx::HttpMethod;
use crate::DocsContext;
use crate::registry::{DocsRegistry, NavGroup};
#[component]
pub fn DocsSidebar() -> Element {
let registry = use_context::<&'static DocsRegistry>();
let active_tab = use_context::<Signal<String>>();
let nav = ®istry.nav;
let groups: Vec<&NavGroup> = if nav.has_tabs() {
nav.groups_for_tab(&active_tab())
} else {
nav.groups.iter().collect()
};
rsx! {
nav { class: "space-y-6",
for group in groups.iter() {
SidebarGroup { group: (*group).clone() }
}
}
}
}
#[component]
fn SidebarGroup(group: NavGroup) -> Element {
let registry = use_context::<&'static DocsRegistry>();
let api_entries = registry.get_api_sidebar_entries();
let is_api_group = group.group == registry.api_group_name;
if is_api_group {
rsx! {
div { class: "space-y-2",
h3 { class: "font-semibold text-sm text-base-content/70 uppercase tracking-wider px-3",
"{group.group}"
}
ul { class: "space-y-1",
for page in group.pages.iter() {
SidebarLink { path: page.clone() }
}
}
for (tag, entries) in api_entries.iter() {
div { class: "mt-3",
h4 { class: "text-xs font-medium text-base-content/50 uppercase tracking-wider px-3 mb-1",
"{tag.name}"
}
ul { class: "space-y-0.5",
for entry in entries.iter() {
ApiSidebarLink {
slug: entry.slug.clone(),
title: entry.title.clone(),
method: entry.method,
}
}
}
}
}
}
}
} else {
rsx! {
div { class: "space-y-2",
h3 { class: "font-semibold text-sm text-base-content/70 uppercase tracking-wider px-3",
"{group.group}"
}
ul { class: "space-y-1",
for page in group.pages.iter() {
SidebarLink { path: page.clone() }
}
}
}
}
}
}
#[component]
fn ApiSidebarLink(slug: String, title: String, method: HttpMethod) -> Element {
let ctx = use_context::<DocsContext>();
let registry = use_context::<&'static DocsRegistry>();
let prefix = registry.get_first_api_prefix().unwrap_or("api-reference");
let path = format!("{prefix}/{slug}");
let is_active = (ctx.current_path)() == path;
let active_class = if is_active {
"bg-primary/10 text-primary font-medium border-l-2 border-primary"
} else {
"text-base-content/70 hover:text-base-content hover:bg-base-200"
};
let badge_class = method.badge_class();
let method_label = match method {
HttpMethod::Delete => "DEL",
_ => method.as_str(),
};
let href = format!("{}/{}", ctx.base_path, path);
rsx! {
li {
Link {
to: NavigationTarget::Internal(href),
class: "flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg transition-colors {active_class}",
span { class: "badge badge-xs font-mono font-bold {badge_class} shrink-0",
"{method_label}"
}
span { class: "truncate", "{title}" }
}
}
}
}
#[component]
fn SidebarLink(path: String) -> Element {
let ctx = use_context::<DocsContext>();
let registry = use_context::<&'static DocsRegistry>();
let title = registry.get_sidebar_title(&path).unwrap_or_else(|| {
path.split('/')
.next_back()
.unwrap_or(&path)
.replace('-', " ")
});
let current = (ctx.current_path)();
let is_active = current == path || (current.is_empty() && path == registry.default_path);
let active_class = if is_active {
"bg-primary/10 text-primary font-medium border-l-2 border-primary"
} else {
"text-base-content/70 hover:text-base-content hover:bg-base-200"
};
let href = format!("{}/{}", ctx.base_path, path);
rsx! {
li {
Link {
to: NavigationTarget::Internal(href),
class: "block px-3 py-2 text-sm rounded-lg transition-colors {active_class}",
"{title}"
}
}
}
}