Skip to main content

dioxus_docs_kit/components/
page_nav.rs

1use dioxus::prelude::*;
2
3use crate::DocsContext;
4use crate::registry::DocsRegistry;
5
6/// Page navigation (previous/next).
7#[component]
8pub fn DocsPageNav(current_path: String) -> Element {
9    let registry = use_context::<&'static DocsRegistry>();
10    let ctx = use_context::<DocsContext>();
11    let nav = &registry.nav;
12
13    // Determine which tab the current page belongs to
14    let current_tab = registry.tab_for_path(&current_path);
15
16    // Build page list scoped to the current tab
17    let tab_groups: Vec<_> = if let Some(ref tab) = current_tab {
18        nav.groups_for_tab(tab)
19    } else {
20        nav.groups.iter().collect()
21    };
22
23    let api_prefix = registry.get_first_api_prefix();
24    let overview_path = api_prefix.map(|p| format!("{p}/overview"));
25
26    let mut all_pages: Vec<String> = Vec::new();
27    for group in &tab_groups {
28        for page in &group.pages {
29            all_pages.push(page.clone());
30            // Insert API endpoint pages right after overview
31            if let Some(ref ov) = overview_path
32                && page == ov
33            {
34                all_pages.extend(registry.get_api_endpoint_paths());
35            }
36        }
37    }
38
39    let current_index = all_pages.iter().position(|p| *p == current_path);
40
41    let prev_page = current_index.and_then(|i| {
42        if i > 0 {
43            Some(all_pages[i - 1].clone())
44        } else {
45            None
46        }
47    });
48
49    let next_page = current_index.and_then(|i| {
50        if i + 1 < all_pages.len() {
51            Some(all_pages[i + 1].clone())
52        } else {
53            None
54        }
55    });
56
57    rsx! {
58        nav { class: "mt-16 pt-8 border-t border-base-300 flex justify-between gap-4",
59            // Previous link
60            div { class: "flex-1",
61                if let Some(prev) = prev_page {
62                    {
63                        let title = registry.get_sidebar_title(&prev).unwrap_or_else(|| prev.clone());
64                        let href = format!("{}/{}", ctx.base_path, prev);
65                        rsx! {
66                            Link {
67                                to: NavigationTarget::Internal(href),
68                                class: "group flex flex-col p-4 rounded-lg border border-base-300 hover:border-primary/50 hover:bg-base-200/50 transition-all",
69                                span { class: "text-xs text-base-content/50 mb-1", "Previous" }
70                                span { class: "font-medium group-hover:text-primary transition-colors",
71                                    "{title}"
72                                }
73                            }
74                        }
75                    }
76                }
77            }
78
79            // Next link
80            div { class: "flex-1 text-right",
81                if let Some(next) = next_page {
82                    {
83                        let title = registry.get_sidebar_title(&next).unwrap_or_else(|| next.clone());
84                        let href = format!("{}/{}", ctx.base_path, next);
85                        rsx! {
86                            Link {
87                                to: NavigationTarget::Internal(href),
88                                class: "group flex flex-col p-4 rounded-lg border border-base-300 hover:border-primary/50 hover:bg-base-200/50 transition-all items-end",
89                                span { class: "text-xs text-base-content/50 mb-1", "Next" }
90                                span { class: "font-medium group-hover:text-primary transition-colors",
91                                    "{title}"
92                                }
93                            }
94                        }
95                    }
96                }
97            }
98        }
99    }
100}