leptos_use/
use_favicon.rs

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