dioxus_docs_kit/components/blog/
blog_layout.rs1use dioxus::prelude::*;
2use dioxus_free_icons::Icon;
3use dioxus_free_icons::icons::ld_icons::{LdMenu, LdSearch};
4
5use crate::blog::registry::BlogRegistry;
6use crate::components::docs_layout::{CurrentTheme, DrawerOpen};
7
8use super::mobile_drawer::BlogMobileDrawer;
9use super::search_modal::BlogSearchModal;
10use super::theme_toggle::BlogThemeToggle;
11
12#[component]
19pub fn BlogLayout(
20 header: Option<Element>,
21 #[props(default = true)] show_header: bool,
22 children: Element,
23) -> Element {
24 let registry = use_context::<&'static BlogRegistry>();
25
26 let parent_search: Option<Signal<bool>> = try_use_context();
27 let parent_drawer: Option<DrawerOpen> = try_use_context();
28
29 let local_search = use_signal(|| false);
30 let local_drawer = use_signal(|| false);
31
32 let mut search_open = parent_search.unwrap_or(local_search);
33 let mut drawer_open = parent_drawer.map(|d| d.0).unwrap_or(local_drawer);
34
35 use_context_provider(|| search_open);
36 use_context_provider(|| DrawerOpen(drawer_open));
37
38 let theme_default = registry
40 .theme
41 .as_ref()
42 .map(|t| t.default_theme.clone())
43 .unwrap_or_default();
44 let theme_storage_key = registry
45 .theme
46 .as_ref()
47 .map(|t| t.storage_key.clone())
48 .unwrap_or_default();
49 let has_theme = registry.theme.is_some();
50
51 let mut current_theme = use_signal(|| theme_default.clone());
52 use_context_provider(|| CurrentTheme(current_theme));
53
54 use_effect(move || {
55 if !has_theme {
56 return;
57 }
58 let key = theme_storage_key.clone();
59 let fallback = theme_default.clone();
60 spawn(async move {
61 let mut eval = document::eval(&format!(
62 r#"
63 let theme = null;
64 try {{ theme = localStorage.getItem('{key}'); }} catch(e) {{}}
65 theme = theme || '{fallback}';
66 document.documentElement.setAttribute('data-theme', theme);
67 dioxus.send(theme);
68 "#
69 ));
70 if let Ok(stored) = eval.recv::<String>().await {
71 current_theme.set(stored);
72 }
73 });
74 });
75
76 use_effect(move || {
78 spawn(async move {
79 let mut eval = document::eval(
80 r#"
81 document.addEventListener('keydown', (e) => {
82 if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
83 e.preventDefault();
84 dioxus.send(true);
85 }
86 });
87 while (true) { await new Promise(r => setTimeout(r, 1000000)); }
88 "#,
89 );
90 loop {
91 if (eval.recv::<bool>().await).is_ok() {
92 search_open.toggle();
93 }
94 }
95 });
96 });
97
98 rsx! {
99 div { class: "min-h-screen bg-base-100",
100 if show_header {
101 div { class: "sticky top-0 z-50",
102 if let Some(hdr) = header {
103 {hdr}
104 } else {
105 div { class: "navbar bg-base-200 border-b border-base-300 px-4 lg:px-8",
106 div { class: "flex-1 gap-2",
107 button {
108 class: "btn btn-ghost btn-sm btn-square lg:hidden",
109 onclick: move |_| drawer_open.toggle(),
110 Icon { class: "size-5", icon: LdMenu }
111 }
112 }
113 div { class: "flex-none gap-1",
114 BlogSearchButton { search_open }
115 BlogThemeToggle {}
116 }
117 }
118 }
119 }
120 }
121
122 div { class: "flex-1 min-w-0",
123 {children}
124 }
125 }
126
127 BlogMobileDrawer { open: drawer_open }
128 BlogSearchModal {}
129 }
130}
131
132#[component]
134pub fn BlogSearchButton(search_open: Signal<bool>) -> Element {
135 rsx! {
136 button {
137 class: "btn btn-ghost btn-sm gap-2",
138 onclick: move |_| search_open.set(true),
139 Icon { class: "size-4", icon: LdSearch }
140 span { class: "hidden sm:inline text-base-content/60 text-sm", "Search" }
141 kbd { class: "kbd kbd-xs hidden sm:inline-flex", "\u{2318}K" }
142 }
143 }
144}