yggdrasil/components/
footer.rs1#[cfg(target_arch = "wasm32")]
7use crate::hooks::event_listener::use_event_listener;
8use dioxus::prelude::*;
9
10#[component]
18#[allow(unused_mut)]
19pub fn Footer() -> Element {
20 let mut visible = use_signal(|| false);
21
22 let mut sync_visible = move || {
25 #[cfg(target_arch = "wasm32")]
26 {
27 if let Some(w) = web_sys::window() {
28 let threshold = w
29 .inner_height()
30 .ok()
31 .and_then(|h| h.as_f64())
32 .unwrap_or(0.0);
33 let scroll_y = w.scroll_y().unwrap_or(0.0);
34 visible.set(scroll_y > threshold);
35 }
36 }
37 };
38
39 #[cfg(target_arch = "wasm32")]
44 use_event_listener(
45 web_sys::window,
46 "scroll",
47 sync_visible,
49 );
50
51 use_effect(move || {
53 sync_visible();
54 });
55
56 let btn_class = use_memo(move || {
58 let base = "fixed bottom-16 right-8 z-50 w-10 h-10 rounded-full bg-paper-entry border border-paper-border shadow-sm flex items-center justify-center cursor-pointer transition-all duration-300 text-paper-secondary hover:text-paper-accent";
59 if visible() {
60 format!("{} opacity-100 translate-y-0", base)
61 } else {
62 format!("{} opacity-0 translate-y-2 pointer-events-none", base)
63 }
64 });
65
66 rsx! {
67 footer { class: "w-full border-t border-paper-border mt-auto",
68 div { class: "max-w-3xl mx-auto px-6 py-5 flex items-center justify-between text-sm text-paper-secondary",
69 span { "© 2026 Yggdrasil" }
70 }
71 }
72 a {
73 class: "{btn_class}",
74 href: "#top",
75 aria_label: "go to top",
76 title: "Go to Top (Alt + G)",
77 accesskey: "g",
78 onclick: move |evt| {
79 evt.prevent_default();
80 scroll_to_top();
81 },
82 svg {
83 xmlns: "http://www.w3.org/2000/svg",
84 height: "24px",
85 view_box: "0 -960 960 960",
86 width: "24px",
87 fill: "currentColor",
88 path { d: "m296-224-56-56 240-240 240 240-56 56-184-183-184 183Zm0-240-56-56 240-240 240 240-56 56-184-183-184 183Z" }
89 }
90 }
91 }
92}
93
94fn scroll_to_top() {
98 #[cfg(target_arch = "wasm32")]
99 {
100 if let Some(window) = web_sys::window() {
101 let options = web_sys::ScrollToOptions::new();
102 options.set_top(0.0);
103 options.set_behavior(web_sys::ScrollBehavior::Smooth);
104 window.scroll_to_with_scroll_to_options(&options);
105
106 if let Ok(history) = window.history() {
107 let _ = history.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(" "));
108 }
109 }
110 }
111}