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}