dioxus_docs_kit/components/
shared.rs1use dioxus::prelude::*;
4use dioxus_free_icons::Icon;
5use dioxus_free_icons::icons::ld_icons::{LdMoon, LdSun};
6
7use super::docs_layout::CurrentTheme;
8use crate::config::ThemeConfig;
9
10pub fn use_theme_provider(theme: Option<ThemeConfig>) -> Signal<String> {
18 let theme_default = theme
19 .as_ref()
20 .map(|t| t.default_theme.clone())
21 .unwrap_or_default();
22 let storage_key = theme
23 .as_ref()
24 .map(|t| t.storage_key.clone())
25 .unwrap_or_default();
26 let has_theme = theme.is_some();
27
28 let mut current_theme = use_signal(|| theme_default.clone());
29 use_context_provider(|| CurrentTheme(current_theme));
30
31 use_effect(move || {
33 if !has_theme {
34 return;
35 }
36 let key = storage_key.clone();
37 let fallback = theme_default.clone();
38 spawn(async move {
39 let mut eval = document::eval(&format!(
40 r#"
41 let theme = null;
42 try {{ theme = localStorage.getItem('{key}'); }} catch(e) {{}}
43 theme = theme || '{fallback}';
44 document.documentElement.setAttribute('data-theme', theme);
45 dioxus.send(theme);
46 "#
47 ));
48 if let Ok(stored) = eval.recv::<String>().await {
49 current_theme.set(stored);
50 }
51 });
52 });
53
54 current_theme
55}
56
57pub(crate) fn use_search_hotkey(mut search_open: Signal<bool>) {
62 let owner = dioxus::core::current_scope_id().0;
63 use_effect(move || {
64 spawn(async move {
65 let mut eval = document::eval(&format!(
66 r#"
67 window.__dkSearchCleanup?.stop();
68 const handler = (e) => {{
69 if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'k') {{
70 e.preventDefault();
71 dioxus.send(true);
72 }}
73 }};
74 window.__dkSearchHotkey = handler;
75 document.addEventListener('keydown', handler);
76 await new Promise(resolve => {{
77 window.__dkSearchCleanup = {{ owner: {owner}, stop: () => {{
78 document.removeEventListener('keydown', handler);
79 if (window.__dkSearchHotkey === handler) {{
80 delete window.__dkSearchHotkey;
81 delete window.__dkSearchCleanup;
82 }}
83 resolve();
84 }} }};
85 }});
86 "#,
87 ));
88 while eval.recv::<bool>().await.is_ok() {
91 search_open.toggle();
92 }
93 });
94 });
95 use_drop(move || {
96 let _ = document::eval(&format!(
97 "if (window.__dkSearchCleanup?.owner === {owner}) window.__dkSearchCleanup.stop();"
98 ));
99 });
100}
101
102#[component]
107pub(crate) fn ThemeToggleButton(theme: Option<ThemeConfig>) -> Element {
108 let toggle = match theme.as_ref().and_then(|t| t.toggle_themes.as_ref()) {
109 Some(t) => t.clone(),
110 None => return rsx! {},
111 };
112
113 let storage_key = theme
114 .as_ref()
115 .map(|t| t.storage_key.clone())
116 .unwrap_or_default();
117
118 let CurrentTheme(mut current_theme) = use_context::<CurrentTheme>();
119
120 let (light, dark) = toggle;
121 let is_dark = current_theme() == dark;
122
123 rsx! {
124 button {
125 class: "btn btn-ghost btn-sm btn-square",
126 title: if is_dark { "Switch to light mode" } else { "Switch to dark mode" },
127 onclick: move |_| {
128 let new_theme = if (current_theme)() == dark { light.clone() } else { dark.clone() };
129 current_theme.set(new_theme.clone());
130 let key = storage_key.clone();
131 spawn(async move {
132 let _ = document::eval(&format!(
133 r#"document.documentElement.setAttribute('data-theme', '{new_theme}');
134 try {{ localStorage.setItem('{key}', '{new_theme}'); }} catch(e) {{}}"#
135 ));
136 });
137 },
138 if is_dark {
139 Icon { class: "size-5", icon: LdSun }
140 } else {
141 Icon { class: "size-5", icon: LdMoon }
142 }
143 }
144 }
145}