yggdrasil/components/
frontend_layout.rs1use 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
22fn 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#[component]
71pub fn FrontendLayout() -> Element {
72 let route = use_route::<Route>();
73 let nav_items = build_nav_items(route.clone());
74 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#[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}