leptos_use/use_favicon.rs
1#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
2
3use crate::core::MaybeRwSignal;
4use default_struct_builder::DefaultBuilder;
5use leptos::prelude::*;
6use leptos::reactive::wrappers::read::Signal;
7use wasm_bindgen::JsCast;
8
9/// Reactive favicon.
10///
11/// ## Demo
12///
13/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_favicon)
14///
15/// ## Usage
16///
17/// ```
18/// # use leptos::prelude::*;
19/// # use leptos_use::use_favicon;
20/// #
21/// # #[component]
22/// # fn Demo() -> impl IntoView {
23/// #
24/// let (icon, set_icon) = use_favicon();
25///
26/// set_icon.set(Some("dark.png".to_string())); // change current icon
27/// #
28/// # view! { }
29/// # }
30/// ```
31///
32/// ## Passing a Source Signal
33///
34/// You can pass a `Signal` to [`use_favicon_with_options`]. Change from the source signal will be
35/// reflected in your favicon automatically.
36///
37/// ```
38/// # use leptos::prelude::*;
39/// # use leptos_use::{use_favicon_with_options, UseFaviconOptions, use_preferred_dark};
40/// #
41/// # #[component]
42/// # fn Demo() -> impl IntoView {
43/// #
44/// let is_dark = use_preferred_dark();
45///
46/// let (icon, _) = use_favicon_with_options(
47/// UseFaviconOptions::default().new_icon(
48/// Signal::derive(move || {
49/// Some((if is_dark.get() { "dark.png" } else { "light.png" }).to_string())
50/// }),
51/// )
52/// );
53/// #
54/// # view! { }
55/// # }
56/// ```
57///
58/// ## Server-Side Rendering
59///
60/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
61///
62/// On the server only the signals work but no favicon will be changed obviously.
63pub fn use_favicon() -> (Signal<Option<String>>, WriteSignal<Option<String>>) {
64 use_favicon_with_options(UseFaviconOptions::default())
65}
66
67/// Version of [`use_favicon`] that accepts a `UseFaviconOptions`. See [`use_favicon`] for more details.
68pub fn use_favicon_with_options(
69 options: UseFaviconOptions,
70) -> (Signal<Option<String>>, WriteSignal<Option<String>>) {
71 let UseFaviconOptions {
72 new_icon,
73 base_url,
74 rel,
75 } = options;
76
77 let (favicon, set_favicon) = new_icon.into_signal();
78
79 #[cfg(not(feature = "ssr"))]
80 {
81 let link_selector = format!("link[rel*=\"{rel}\"]");
82
83 let apply_icon = move |icon: &String| {
84 if let Some(head) = document().head()
85 && let Ok(links) = head.query_selector_all(&link_selector)
86 {
87 let href = format!("{base_url}{icon}");
88
89 for i in 0..links.length() {
90 let node = links.get(i).expect("checked length");
91 let link: web_sys::HtmlLinkElement = node.unchecked_into();
92 link.set_href(&href);
93 }
94 }
95 };
96
97 Effect::watch(
98 move || favicon.get(),
99 move |new_icon, prev_icon, _| {
100 if Some(new_icon) != prev_icon
101 && let Some(new_icon) = new_icon
102 {
103 apply_icon(new_icon);
104 }
105 },
106 false,
107 );
108 }
109
110 (favicon, set_favicon)
111}
112
113/// Options for [`use_favicon_with_options`].
114#[derive(DefaultBuilder)]
115pub struct UseFaviconOptions {
116 /// New input favicon. Can be a `RwSignal` in which case updates will change the favicon. Defaults to None.
117 #[builder(into)]
118 new_icon: MaybeRwSignal<Option<String>>,
119
120 /// Base URL of the favicon. Defaults to "".
121 #[builder(into)]
122 base_url: String,
123 /// Rel attribute of the <link> tag. Defaults to "icon".
124 #[builder(into)]
125 rel: String,
126}
127
128impl Default for UseFaviconOptions {
129 fn default() -> Self {
130 Self {
131 new_icon: Default::default(),
132 base_url: "".to_string(),
133 rel: "icon".to_string(),
134 }
135 }
136}