Skip to main content

yggdrasil/components/
frontend_layout.rs

1//! 前台布局组件
2//!
3//! 包裹所有前台路由,提供统一的 Header、Footer 与主内容区容器,
4//! 并为不同路由在 SuspenseBoundary 中展示对应的骨架屏。
5
6use dioxus::prelude::*;
7
8use crate::components::footer::Footer;
9use crate::components::header::{Header, SearchIconLink};
10use crate::components::nav::build_nav_items;
11use crate::components::skeletons::archive_skeleton::ArchiveSkeleton;
12use crate::components::skeletons::changelog_skeleton::ChangelogSkeleton;
13use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
14use crate::components::skeletons::friends_skeleton::FriendsSkeleton;
15use crate::components::skeletons::home_skeleton::HomeSkeleton;
16use crate::components::skeletons::post_detail_skeleton::PostDetailSkeleton;
17use crate::components::skeletons::search_skeleton::SearchSkeleton;
18use crate::components::skeletons::tags_skeleton::TagDetailSkeleton;
19use crate::router::Route;
20use crate::theme::ThemeToggle;
21
22/// 根据当前前台路由选择对应的骨架屏组件。
23fn route_skeleton(route: &Route) -> Element {
24    match route {
25        Route::Notes {} | Route::NoteDetail { .. } | Route::NotebookDetail { .. } => rsx! {
26            DelayedSkeleton { crate::pages::notes::NotesSkeleton {} }
27        },
28        Route::HomePage { page } => rsx! {
29            DelayedSkeleton { HomeSkeleton { current_page: (*page).max(1) } }
30        },
31        Route::Archives {} => rsx! {
32            DelayedSkeleton { ArchiveSkeleton {} }
33        },
34        Route::TagDetail { tag } => rsx! {
35            TagDetailSkeleton { tag: tag.clone() }
36        },
37        Route::Friends {} => rsx! {
38            DelayedSkeleton { FriendsSkeleton {} }
39        },
40        Route::Search {} => rsx! {
41            DelayedSkeleton { SearchSkeleton {} }
42        },
43        Route::PostDetail { .. } => rsx! {
44            DelayedSkeleton { PostDetailSkeleton {} }
45        },
46        Route::Changelog {} => rsx! {
47            DelayedSkeleton { ChangelogSkeleton {} }
48        },
49        Route::WritingGuide {} => rsx! {
50            DelayedSkeleton {
51                p { class: "py-20 text-sm text-paper-secondary", role: "status", "正在载入写作指南…" }
52            }
53        },
54        Route::ComponentShowcase {} | Route::ComponentDetail { .. } => rsx! {
55            div { class: "py-20", role: "status", "正在翻开组件图鉴…" }
56        },
57        Route::NotFound { .. } => rsx! {
58            div { class: "py-20 md:py-28" }
59        },
60        _ => rsx! {
61            DelayedSkeleton { HomeSkeleton {} }
62        },
63    }
64}
65
66/// 前台整体布局组件。
67///
68/// 负责渲染 Header(含前台导航与主题切换)、主内容区与 Footer,
69/// 并在路由内容加载过程中显示与路由匹配的骨架屏。
70#[component]
71pub fn FrontendLayout() -> Element {
72    let route = use_route::<Route>();
73    let nav_items = build_nav_items(route.clone());
74    // 首页与其它前台页面共用阅读宽度,导航和正文保持对齐。
75    let max_width = if matches!(
76        route,
77        Route::ComponentShowcase {} | Route::ComponentDetail { .. }
78    ) {
79        "max-w-6xl"
80    } else {
81        "max-w-4xl"
82    };
83
84    rsx! {
85        FrontendShell {
86            max_width,
87            header: rsx! { Header {
88                max_width,
89                nav_items,
90                right_content: rsx! {
91                    SearchIconLink {}
92                    ThemeToggle {}
93                },
94            } },
95            footer: rsx! { Footer {} },
96            main_content: rsx! {
97                SuspenseBoundary { fallback: move |_| route_skeleton(&route), Outlet::<Route> {} }
98            },
99        }
100    }
101}
102
103/// 前台页面与图鉴样例共用的布局关系;路由内容由外层提供。
104#[component]
105pub(crate) fn FrontendShell(
106    header: Element,
107    main_content: Element,
108    footer: Element,
109    #[props(default = "max-w-4xl")] max_width: &'static str,
110    #[props(default = "min-h-screen")] min_height: &'static str,
111) -> Element {
112    rsx! {
113        div { class: "{min_height} flex flex-col bg-paper-theme",
114            {header}
115            main { class: "flex-1 w-full {max_width} mx-auto px-6 py-6 md:py-12 overflow-x-clip",
116                {main_content}
117            }
118            {footer}
119        }
120    }
121}