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///
8/// Page order follows `_nav.json`. API endpoint pages are included in the
9/// ordering only if the owning spec's nav group contains a page named
10/// `<prefix>/overview` — endpoints are inserted right after it. Without an
11/// overview page, endpoint pages render without prev/next links.
12#[component]
13pub fn DocsPageNav(current_path: String) -> Element {
14    let registry = use_context::<&'static DocsRegistry>();
15    let ctx = use_context::<DocsContext>();
16    let nav = &registry.nav;
17
18    // Determine which tab the current page belongs to
19    let current_tab = registry.tab_for_path(&current_path);
20
21    // Build page list scoped to the current tab
22    let tab_groups: Vec<_> = if let Some(ref tab) = current_tab {
23        nav.groups_for_tab(tab)
24    } else {
25        nav.groups.iter().collect()
26    };
27
28    let mut all_pages: Vec<String> = Vec::new();
29    for group in &tab_groups {
30        for page in &group.pages {
31            all_pages.push(page.clone());
32            // Insert a spec's endpoint pages right after its "<prefix>/overview"
33            // page, so endpoints participate in prev/next ordering.
34            if let Some(prefix) = page.strip_suffix("/overview")
35                && let Some(spec) = registry.get_api_spec(prefix)
36            {
37                all_pages.extend(
38                    spec.operations
39                        .iter()
40                        .map(|op| format!("{prefix}/{}", op.slug())),
41                );
42            }
43        }
44    }
45
46    let current_index = all_pages.iter().position(|p| *p == current_path);
47
48    let prev_page = current_index.and_then(|i| {
49        if i > 0 {
50            Some(all_pages[i - 1].clone())
51        } else {
52            None
53        }
54    });
55
56    let next_page = current_index.and_then(|i| {
57        if i + 1 < all_pages.len() {
58            Some(all_pages[i + 1].clone())
59        } else {
60            None
61        }
62    });
63
64    rsx! {
65        nav { class: "dk-pagination mt-16 pt-8 border-t border-base-300 flex justify-between gap-4",
66            // Previous link
67            div { class: "flex-1",
68                if let Some(prev) = prev_page {
69                    {
70                        let title = registry.get_sidebar_title(&prev).unwrap_or_else(|| prev.clone());
71                        let href = format!("{}/{}", ctx.base_path, prev);
72                        rsx! {
73                            Link {
74                                to: NavigationTarget::Internal(href),
75                                class: "dk-page-prev group flex flex-col p-4 rounded-lg border border-base-300 hover:border-primary/50 hover:bg-base-200/50 transition-all",
76                                span { class: "text-xs text-base-content/50 mb-1", "Previous" }
77                                span { class: "font-medium group-hover:text-primary transition-colors",
78                                    "{title}"
79                                }
80                            }
81                        }
82                    }
83                }
84            }
85
86            // Next link
87            div { class: "flex-1 text-right",
88                if let Some(next) = next_page {
89                    {
90                        let title = registry.get_sidebar_title(&next).unwrap_or_else(|| next.clone());
91                        let href = format!("{}/{}", ctx.base_path, next);
92                        rsx! {
93                            Link {
94                                to: NavigationTarget::Internal(href),
95                                class: "dk-page-next 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",
96                                span { class: "text-xs text-base-content/50 mb-1", "Next" }
97                                span { class: "font-medium group-hover:text-primary transition-colors",
98                                    "{title}"
99                                }
100                            }
101                        }
102                    }
103                }
104            }
105        }
106    }
107}