yew_notifications/
provider.rs

1use std::marker::PhantomData;
2
3use yew::{
4    classes, function_component, html, use_effect_with, use_reducer_eq, Callback, Children, Classes, ContextProvider,
5    Html, Properties,
6};
7
8use crate::manager::{Action, NotificationsList};
9use crate::{Notifiable, NotifiableComponentFactory, NotificationsManager};
10
11const NOTIFICATION_PROVIDER_STYLE: &str = include_str!("../static/notifications_provider.scss");
12
13/// Notifications position on the screen
14#[derive(Debug, Clone, PartialEq)]
15pub enum NotificationsPosition {
16    /// Spawned notifications will be places in the top left corner of the screen
17    TopLeft,
18    /// Spawned notifications will be places in the top right corner of the screen
19    TopRight,
20    /// Spawned notifications will be places in the bottom right corner of the screen
21    BottomRight,
22    /// Spawned notifications will be places in the bottom left corner of the screen
23    BottomLeft,
24    /// Can be used to specify custom css class for the notifications container
25    ///
26    /// # Note
27    /// The Custom class will overwrite any default provider CSS including style, position, etc.
28    Custom(Classes),
29}
30
31impl From<&NotificationsPosition> for Vec<Classes> {
32    fn from(position: &NotificationsPosition) -> Self {
33        let position = match position {
34            NotificationsPosition::TopLeft => classes!("notifications-provider-top-left"),
35            NotificationsPosition::TopRight => classes!("notifications-provider-top-right"),
36            NotificationsPosition::BottomRight => classes!("notifications-provider-bottom-right"),
37            NotificationsPosition::BottomLeft => classes!("notifications-provider-bottom-left"),
38            NotificationsPosition::Custom(classes) => return vec![classes.clone()],
39        };
40        vec![classes!("notifications"), position]
41    }
42}
43
44impl From<&str> for NotificationsPosition {
45    fn from(position: &str) -> Self {
46        match position {
47            "top-left" => Self::TopLeft,
48            "top-right" => Self::TopRight,
49            "bottom-left" => Self::BottomLeft,
50            "bottom-right" => Self::BottomRight,
51            p => Self::Custom(classes!(p.to_owned())),
52        }
53    }
54}
55
56/// Props for [`NotificationsProvider`]
57#[derive(Properties, PartialEq, Clone)]
58pub struct NotificationsProviderProps<N: Notifiable + PartialEq, F: NotifiableComponentFactory<N> + PartialEq + Clone> {
59    /// Inner provider components
60    pub children: Children,
61    /// Instance of the component factory
62    pub component_creator: F,
63    /// Notifications position on the screen
64    ///
65    /// Default position is bottom right.
66    #[prop_or(NotificationsPosition::BottomRight)]
67    pub position: NotificationsPosition,
68    #[prop_or_default]
69    pub _notification: PhantomData<N>,
70}
71
72/// The notification provider component.
73///
74/// Every child (direct or indirect) of this component can use `use_notification` hook to spawn new notifications.
75/// `N` - type of the notification.
76/// `F` - notification factory type.
77///
78/// # Example
79///
80/// ```
81/// let component_creator = NotificationFactory::default();
82///
83/// html! {
84///     <NotificationsProvider<Notification, NotificationFactory> {component_creator}>
85///         <MyComponent />
86///     </NotificationsProvider<Notification, NotificationFactory>>
87/// }
88/// ```
89#[function_component(NotificationsProvider)]
90pub fn notifications_provider<
91    N: Notifiable + PartialEq + Clone,
92    F: NotifiableComponentFactory<N> + PartialEq + Clone,
93>(
94    props: &NotificationsProviderProps<N, F>,
95) -> Html {
96    let notifications = use_reducer_eq(NotificationsList::<N>::default);
97
98    let manager = NotificationsManager {
99        sender: Some(notifications.dispatcher()),
100    };
101
102    use_effect_with(
103        (!notifications.is_empty(), notifications.dispatcher()),
104        |(is_active, sender)| {
105            use gloo::timers::callback::Interval;
106
107            let sender = sender.clone();
108            let is_active = *is_active;
109
110            let interval = Interval::new(NotificationsList::<N>::TIME_TICK_MILLIS as u32, move || {
111                if is_active {
112                    sender.dispatch(Action::Tick);
113                }
114            });
115
116            move || drop(interval)
117        },
118    );
119
120    let ns = notifications.notifications.clone();
121    let children = props.children.clone();
122    let dispatcher = notifications.dispatcher();
123
124    let notification_creator = &props.component_creator;
125
126    let classes: Vec<Classes> = (&props.position).into();
127
128    let notification_style = {
129        #[cfg(feature = "standard-notification")]
130        {
131            include_str!("../static/notification.scss")
132        }
133
134        #[cfg(not(feature = "standard-notification"))]
135        {
136            ""
137        }
138    };
139
140    html! {
141        <ContextProvider<NotificationsManager<N>> context={manager}>
142            {children}
143            <style>
144                {NOTIFICATION_PROVIDER_STYLE}
145                {notification_style}
146            </style>
147            <div class={classes}>
148                {for ns.iter().map(|n| {
149                    let notification = n.clone();
150                    let id = notification.id();
151
152                    let onclick = {
153                        let dispatcher = dispatcher.clone();
154                        Callback::from(move |_| {
155                            dispatcher.dispatch(Action::Close(id));
156                        })
157                    };
158
159                    let onenter = {
160                        let dispatcher = dispatcher.clone();
161                        Callback::from(move |_| {
162                            dispatcher.dispatch(Action::Pause(id));
163                        })
164                    };
165
166                    let onleave = {
167                        let dispatcher = dispatcher.clone();
168                        Callback::from(move |_| {
169                            dispatcher.dispatch(Action::Continue(id));
170                        })
171                    };
172
173                    notification_creator.component(notification, onclick, onenter, onleave)
174                })}
175            </div>
176        </ContextProvider<NotificationsManager<N>>>
177    }
178}