Skip to main content

euv_ui/component/notification/view/
fn.rs

1use super::*;
2
3/// A notification stack pinned to the top right of the viewport.
4///
5/// The component is purely presentational: it renders the notifications it
6/// is given and reports the clicks through the item callbacks. It owns no
7/// queue and starts no timers, so the auto-dismiss timing stays with the
8/// caller — `use_timer` or `App::use_interval` — and the rendered tree
9/// stays deterministic and testable. The stack container is always
10/// mounted, even when `items` is empty, so a caller can push notifications
11/// in without remounting the component.
12///
13/// # Arguments
14///
15/// - `VirtualNode<EuvNotificationProps>` - The props node containing the notifications.
16///
17/// # Returns
18///
19/// - `VirtualNode` - The notification stack virtual DOM tree.
20#[component]
21pub fn euv_notification(node: VirtualNode<EuvNotificationProps>) -> VirtualNode {
22    let EuvNotificationProps { items }: EuvNotificationProps =
23        node.try_get_props().unwrap_or_default();
24    let notifications: Vec<VirtualNode> = items
25        .into_iter()
26        .enumerate()
27        .map(
28            |(index, item): (usize, notification::view::r#struct::EuvNotificationItem)| {
29                euv_notification_item(index, item)
30            },
31        )
32        .collect();
33    html! {
34        div {
35            class: c_euv_notification_stack()
36            notifications
37        }
38    }
39}
40
41/// Renders one notification: its body and the optional close button.
42///
43/// # Arguments
44///
45/// - `usize` - The index of the notification in the stack, used as the diff key.
46/// - `EuvNotificationItem` - The notification to render, consumed.
47///
48/// # Returns
49///
50/// - `VirtualNode` - The notification virtual DOM tree.
51fn euv_notification_item(index: usize, item: EuvNotificationItem) -> VirtualNode {
52    let EuvNotificationItem {
53        title,
54        text,
55        on_close,
56        dismissible,
57    }: EuvNotificationItem = item;
58    let is_dismissible: bool = dismissible || on_close.is_some();
59    let close_handler: Option<Rc<dyn Fn(Event)>> = notification_callback_handler(on_close);
60    html! {
61        div {
62            key: index.to_string()
63            class: c_euv_notification()
64            div {
65                class: c_euv_notification_body()
66                div {
67                    class: c_euv_notification_title()
68                    title
69                }
70                div {
71                    class: c_euv_notification_text()
72                    text
73                }
74            }
75            if is_dismissible {
76                button {
77                    class: c_euv_notification_close()
78                    onclick: close_handler
79                    "×"
80                }
81            }
82        }
83    }
84}
85
86/// Builds the click handler that fires a caller's notification callback.
87///
88/// The event type is a parameter so the DOM-free test suite can drive the
89/// handler with a stand-in event; the component always instantiates it
90/// with [`Event`].
91///
92/// # Arguments
93///
94/// - `Option<Rc<dyn Fn()>>` - The callback fired by the handler.
95///
96/// # Returns
97///
98/// - `Option<Rc<dyn Fn(E)>>` - The click handler, or `None` when there is no callback.
99pub fn notification_callback_handler<E: 'static>(
100    callback: Option<Rc<dyn Fn()>>,
101) -> Option<Rc<dyn Fn(E)>> {
102    callback.map(|handler: Rc<dyn Fn()>| -> Rc<dyn Fn(E)> {
103        Rc::new(move |_: E| {
104            handler();
105        })
106    })
107}