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}