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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use *;
use BaseBackdrop;
use component_doc;
use ;
use inject_style;
use backdrop_styles;
use BackdropConfig;
/// `Backdrop` renders a scrim over the viewport — full dim for loading or modal blocking,
/// or a spotlight cutout that highlights one element by `anchor_id`. Bind `config.open`;
/// pass `on_click` to dismiss on scrim tap. Prefer composed scrims inside [`Dialog`](crate::Dialog)
/// and [`Drawer`](crate::Drawer) unless you need a standalone loading overlay or spotlight highlight.
/// Pair spotlight cutouts with [`SpotlightTip`](crate::SpotlightTip) and [`SpotlightTour`](crate::SpotlightTour).
///
/// # When to use
///
/// - Modal dialogs and drawers that need a dimmed page behind the surface - Loading states that block interaction until work completes - Any overlay that should signal a temporary state change
///
/// # Usage
///
/// 1. Bind `config.open` to show or hide the scrim. 2. Pass `on_click` when clicking the scrim should dismiss the overlay. 3. Put a [`Spinner`](crate::Spinner) or other content in `children` for loading overlays.
///
/// # Best Practices
///
/// ## Do's
///
/// * Compose Backdrop inside teleported overlays (Dialog, Drawer) rather than duplicating scrim CSS * Provide an explicit dismiss path when `on_click` closes the overlay
///
/// ## Don'ts
///
/// * Do not stack multiple opaque backdrops — one scrim per overlay layer
///
/// # Examples
///
/// ## Dimmed scrim, click to close
/// Toggle the scrim open, then click the dimmed layer to dismiss — the default modal scrim pattern.
/// <!-- default -->
/// <!-- preview -->
/// ```rust
/// use crate::{Backdrop, BackdropConfig, Button};
/// use leptos::prelude::*;
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="backdrop-preview" style="position: relative; min-height: 160px;">
/// <Button on_click=Callback::new(move |_: leptos::ev::MouseEvent| open.set(true))>"Show backdrop"</Button>
/// <Backdrop
/// config=BackdropConfig::new(open.read_only())
/// on_click=Callback::new(move |_: leptos::ev::MouseEvent| open.set(false))
/// />
/// </div>
/// }
/// ```
///
/// ## Loading overlay
/// Center a spinner on the scrim while async work runs.
/// <!-- preview -->
/// ```rust
/// use crate::{Backdrop, BackdropConfig, Spinner};
/// use leptos::prelude::*;
/// view! {
/// <div data-testid="backdrop-loading" style="position: relative; min-height: 160px;">
/// <p style="padding: 16px;">"Loading data…"</p>
/// <Backdrop config=BackdropConfig::new(Signal::from(true)) class="orbital-backdrop--contained".to_string()>
/// <div
/// data-testid="backdrop-spinner"
/// style="position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none;"
/// >
/// <Spinner />
/// </div>
/// </Backdrop>
/// </div>
/// }
/// ```
///
/// ## Bounded demo frame
/// Scrim inside a positioned frame for catalog previews.
/// <!-- preview -->
/// ```rust
/// use crate::{Backdrop, BackdropConfig};
/// use leptos::prelude::*;
/// view! {
/// <div
/// data-testid="backdrop-framed"
/// style="position: relative; width: 320px; height: 180px; overflow: hidden; border: 1px solid var(--orb-color-border-subtle);"
/// >
/// <p style="padding: 16px;">"Content behind the scrim"</p>
/// <Backdrop
/// config=BackdropConfig::new(Signal::from(true))
/// class="backdrop-framed-scrim orbital-backdrop--contained".to_string()
/// />
/// </div>
/// }
/// ```
///
/// ## Spotlight cutout
/// Dim the viewport except a padded hole around a target element by `id`.
/// <!-- preview -->
/// ```rust
/// use crate::{Backdrop, BackdropConfig, Button};
/// use leptos::prelude::*;
/// use orbital_base_components::BackdropMode;
/// let open = RwSignal::new(false);
/// let anchor_id = RwSignal::new(Some("backdrop-spotlight-target".to_string()));
/// view! {
/// <div data-testid="backdrop-spotlight" style="position: relative; min-height: 200px;">
/// <Button on_click=Callback::new(move |_: leptos::ev::MouseEvent| open.set(true))>"Highlight"</Button>
/// <div
/// id="backdrop-spotlight-target"
/// data-testid="backdrop-spotlight-target"
/// style="padding: 12px; margin-top: 12px; border: 1px solid var(--orb-color-border-subtle);"
/// >
/// "Important control"
/// </div>
/// <Backdrop
/// config=BackdropConfig::new(open.read_only()).with_mode(BackdropMode::Spotlight {
/// anchor_id: anchor_id.read_only().into(),
/// padding: 8,
/// })
/// on_click=Callback::new(move |_: leptos::ev::MouseEvent| open.set(false))
/// />
/// </div>
/// }
/// ```