yew_side_effect/
title.rs

1//! A side effect that controls `document.title`.
2//!
3//! To use this side effect, you need to register [`TitleProvider`] like a React Context Provider
4//! in your Application.
5//!
6//! Title can be set with [`Title`] Component.
7//!
8//! Only value provided to the last created [`Title`] will be set.
9use std::rc::Rc;
10
11use crate::{SideEffect, SideEffectProvider, SideEffects};
12use gloo_utils::document;
13
14use yew::prelude::*;
15
16#[doc(hidden)]
17#[derive(Debug, Clone, PartialEq)]
18pub struct TitleSideEffect {
19    value: String,
20}
21
22/// The Properties for Title Provider
23#[derive(Properties, Clone)]
24pub struct TitleProviderProps {
25    /// The default title.
26    pub default_title: String,
27
28    /// A Function to format title.
29    pub format_title: Rc<dyn Fn(&str) -> String>,
30
31    pub children: Children,
32}
33
34#[allow(clippy::vtable_address_comparisons)]
35impl PartialEq for TitleProviderProps {
36    fn eq(&self, rhs: &Self) -> bool {
37        self.default_title == rhs.default_title
38            && self.children == rhs.children
39            && Rc::ptr_eq(&self.format_title, &rhs.format_title)
40    }
41}
42
43/// The Title Provider
44///
45/// You should register this title provider like a react context provider.
46///
47/// It accepts two props, a string `default_title` and a function `format_title`.
48///
49/// ```
50/// use std::rc::Rc;
51/// use yew::prelude::*;
52/// use yew_side_effect::title::TitleProvider;
53///
54/// pub struct App;
55///
56/// impl Component for App {
57///     type Message = ();
58///     type Properties = ();
59///
60///     fn create(_ctx: &Context<Self>) -> Self {
61///         Self
62///     }
63///
64///     fn view(&self, _ctx: &Context<Self>) -> Html {
65///         let children = Html::default();
66///
67///         let format_fn = Rc::new(|m: &str| format!("{} - My Site", m)) as Rc<dyn Fn(&str) -> String>;
68///
69///         html!{
70///             <TitleProvider default_title="My Site" format_title={format_fn}>
71///                 {children}
72///             </TitleProvider>
73///         }
74///     }
75/// }
76/// ```
77#[function_component(TitleProvider)]
78pub fn title_provider(props: &TitleProviderProps) -> Html {
79    let children = props.children.clone();
80    let format_title = props.format_title.clone();
81    let default_title = props.default_title.clone();
82
83    let sync_title = Rc::new(move |titles: SideEffects<TitleSideEffect>| {
84        // Set the last title to the document.
85        let title = if let Some(m) = titles.last().map(|m| m.value.as_ref()) {
86            format_title(m)
87        } else {
88            default_title.clone()
89        };
90
91        document().set_title(&title);
92    }) as Rc<dyn Fn(SideEffects<TitleSideEffect>)>;
93
94    html! {<SideEffectProvider<TitleSideEffect> on_change={sync_title}>{children}</SideEffectProvider<TitleSideEffect>>}
95}
96
97#[doc(hidden)]
98#[derive(Properties, Clone, PartialEq)]
99pub struct TitleProps {
100    pub value: String,
101}
102
103/// Set a title
104///
105///
106/// ```
107/// use yew::prelude::*;
108/// use yew_side_effect::title::Title;
109///
110/// let rendered = html! {<Title value="Homepage" />};
111/// ```
112#[function_component(Title)]
113pub fn title(props: &TitleProps) -> Html {
114    let effect = Rc::new(TitleSideEffect {
115        value: props.value.clone(),
116    });
117
118    html! {<SideEffect<TitleSideEffect> value={effect} />}
119}