1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use crate::futures_signals::signal_vec::SignalVecExt;
use dominator::{events, html, Dom};
use futures_signals::signal::SignalExt;

use crate::components::scrim::*;

pub enum DockPoint {
    TopLeft,
    TopCenter,
    TopRight,
    MiddleLeft,
    MiddleCenter,
    MiddleRight,
    BottomLeft,
    BottomCenter,
    BottomRight,
}

/// Renders an overlay with 9 of dock points for UI elements
/// Elements in the dock positions will hover over the inner view.
/// This can be used to represent dialogs, FABs, status messages etc.
///
/// # Examples
/// ```no_run
/// use dmat_components::components::layouts::dock_overlay::*;
/// use futures_signals::signal::always;
/// use futures_signals::signal::Mutable;
///
/// let show_overlay = Mutable::new(true);
///
/// dock_overlay!({
///  .underlying_view(Some(underlying_view))
///  .overlay_views(vec![
///      (DockPoint::MiddleLeft, card!({.child(text!("Middle Left Dialog!"))})),
///      (DockPoint::MiddleCenter, middle_center_dialog(show_overlay.clone())),
///      (DockPoint::MiddleRight, html!("span", {.text("Middle Right Dialog!")})),
///      (DockPoint::TopLeft, html!("span", {.text("Top Left Dialog!")})),
///      (DockPoint::TopCenter, html!("span", {.text("Top Center Dialog!")})),
///      (DockPoint::TopRight, html!("span", {.text("Top Right Dialog!")})),
///      (DockPoint::BottomLeft, html!("span", {.text("Bottom Left Dialog!")})),
///      (DockPoint::BottomCenter, html!("span", {.text("Bottom Center Dialog!")})),
///      (DockPoint::BottomRight, html!("span", {.text("Bottom Right Dialog!")})),
///  ])
///  .show_scrim(true)
///  .show_overlay_signal(show_overlay.signal())
/// })
/// ```
#[component(render_fn = dock_overlay)]
struct DockOverlay<TOnScrimClick: Fn(events::Click) = fn(events::Click) -> ()> {
    #[signal]
    #[default(None)]
    pub underlying_view: Option<Dom>,
    #[signal_vec]
    #[default(vec ! [])]
    pub overlay_views: (DockPoint, Dom),
    #[signal]
    #[default(false)]
    pub show_scrim: bool,
    #[signal]
    #[default(true)]
    pub show_overlay: bool,
    #[default(| _ | {})]
    pub on_scrim_click: TOnScrimClick,
}

pub fn dock_overlay(props: impl DockOverlayPropsTrait + 'static) -> Dom {
    let DockOverlayProps {
        underlying_view,
        overlay_views,
        show_scrim,
        show_overlay: show_overlay_signal,
        on_scrim_click,
        apply,
    } = props.take();

    let view = scrim(
        ScrimProps::new()
            .content_signal(underlying_view)
            .on_click(on_scrim_click)
            .hide_signal(show_scrim.map(|v| !v)),
    );

    let children = overlay_views.map(|(dock_point, overlay_view)| {
        html!("dom", {
            .class("dmat-overlay-view")
            .class(dock_point_to_css_class(dock_point))
            .child(overlay_view)
        })
    });

    html!("div", {
        .class("dmat-dock-overlay")
        .class_signal("-hidden", show_overlay_signal.map(|v| !v))
        .apply_if(apply.is_some(), |dom| dom.apply(apply.unwrap()))
        .child(view)
        .children_signal_vec(children)
    })
}

fn dock_point_to_css_class(dock_point: DockPoint) -> &'static str {
    match dock_point {
        DockPoint::TopLeft => "top-left",
        DockPoint::TopCenter => "top-center",
        DockPoint::TopRight => "top-right",
        DockPoint::MiddleLeft => "middle-left",
        DockPoint::MiddleCenter => "middle-center",
        DockPoint::MiddleRight => "middle-right",
        DockPoint::BottomLeft => "bottom-left",
        DockPoint::BottomCenter => "bottom-center",
        DockPoint::BottomRight => "bottom-right",
    }
}