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}