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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
use *;
use ;
use component_doc;
use MotionSlot;
use inject_style;
use dialog_styles;
use DialogDismissConfig;
use cratebackdrop_styles;
use crate::;
/// `Dialog` interrupts the page with a teleported modal for confirmations, settings, or destructive choices.
///
/// Bind `open` to a signal your trigger and footer buttons share; set `dismiss.mask_closeable` to false when the user must pick an explicit action. Compose [`DialogSurface`] → [`DialogBody`] → title, content, and [`DialogActions`] — Tab stays inside the panel until you close it.
///
/// # When to use
///
/// - Confirmations, settings panels, or destructive choices that need focus and a scrim
/// - Flows where keyboard users must stay inside the panel until dismissed
///
/// Prefer [`Popover`](crate::Popover) for floating pickers or detail that should not block the page. Prefer [`Drawer`](crate::Drawer) for edge-mounted auxiliary panels.
///
/// # Overlay surfaces
///
/// - **Brief non-interactive hint** — [`Tooltip`](crate::Tooltip)
/// - **Floating panel with content or inputs** — [`Popover`](crate::Popover)
/// - **List of actions from a trigger** — [`Menu`](crate::Menu) or [`MenuButton`](crate::MenuButton)
/// - **Block the page or trap focus** — `Dialog` (this component)
///
/// # Usage
///
/// 1. Create an `RwSignal<bool>` (or `OpenBind`) shared by your trigger and dismiss handlers.
/// 2. Wire an external [`Button`](crate::Button) (or link) to set `open` to `true` — there is no built-in trigger slot; parent-owned state is intentional for Leptos apps.
/// 3. Compose `DialogSurface` → `DialogBody` → [`DialogTitle`], [`DialogContent`], and optional [`DialogActions`].
/// 4. Close from footer buttons or backdrop/Esc by setting `open` to `false`.
///
/// # Best Practices
///
/// ## Do's
///
/// * Pair primary and secondary actions in [`DialogActions`] for destructive flows
/// * Set `dismiss.mask_closeable` to false when accidental backdrop clicks would be harmful
/// * Share one `open` signal between trigger, backdrop dismiss, and footer buttons
///
/// ## Don'ts
///
/// * Do not use Dialog for brief hints — wrap the trigger with [`Tooltip`](crate::Tooltip) instead
/// * Do not use Dialog for non-blocking status — use [`MessageBar`](crate::MessageBar) or [`Toast`](crate::Toast)
///
/// # Examples
///
/// ## Basic confirmation
/// A modal confirmation asks the user to verify an action before continuing. Title summarizes the decision, body explains the outcome, and the footer holds a dismiss action.
/// <!-- preview -->
/// ```rust
/// use crate::{Button, ButtonAppearance};
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="dialog-basic">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open dialog"</Button>
/// <Dialog open=open>
/// <DialogSurface>
/// <DialogBody>
/// <DialogTitle>"Confirm"</DialogTitle>
/// <DialogContent>
/// <div data-testid="dialog-preview">"Save your changes?"</div>
/// </DialogContent>
/// <DialogActions>
/// <Button
/// appearance=ButtonAppearance::Secondary
/// on_click=Callback::new(move |_| open.set(false))
/// >
/// "Cancel"
/// </Button>
/// </DialogActions>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```
///
/// ## Closed by default with trigger
/// Dialogs stay hidden until a trigger sets `open` to true. Bind visibility to a signal the trigger and dismiss actions both read and write.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// let open = RwSignal::new(false);
/// view! {
/// <div>
/// <div data-testid="dialog-trigger">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open dialog"</Button>
/// </div>
/// <Dialog open=open>
/// <DialogSurface>
/// <DialogBody>
/// <DialogTitle>"Settings"</DialogTitle>
/// <DialogContent>
/// <div data-testid="dialog-trigger-content">"Dialog content"</div>
/// </DialogContent>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```
///
/// ## Non-dismissible mask
/// When the user must choose an explicit action, disable backdrop dismiss so accidental clicks cannot close the dialog.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// use super::types::DialogDismissConfig;
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="dialog-modal">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open modal"</Button>
/// <Dialog open=open dismiss=DialogDismissConfig { mask_closeable: Signal::from(false), ..Default::default() }>
/// <DialogSurface>
/// <DialogBody>
/// <DialogContent>
/// <div data-testid="dialog-modal-content">"Must choose an action"</div>
/// </DialogContent>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```
///
/// ## Actions footer
/// Pair primary and secondary actions in the footer so users can confirm or cancel without hunting for controls.
/// <!-- preview -->
/// ```rust
/// use crate::{Button, ButtonAppearance};
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="dialog-actions">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open dialog"</Button>
/// <Dialog open=open>
/// <DialogSurface>
/// <DialogBody>
/// <DialogTitle>"Delete item?"</DialogTitle>
/// <DialogContent>"This cannot be undone."</DialogContent>
/// <DialogActions>
/// <Button appearance=ButtonAppearance::Secondary on_click=Callback::new(move |_| open.set(false))>"Cancel"</Button>
/// <Button appearance=ButtonAppearance::Primary on_click=Callback::new(move |_| open.set(false))>"Delete"</Button>
/// </DialogActions>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```
///
/// ## Escape closes
/// Allow Escape to dismiss routine dialogs; disable for flows that require an explicit choice.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="dialog-esc">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open dialog"</Button>
/// <Dialog open=open>
/// <DialogSurface>
/// <DialogBody>
/// <DialogContent>
/// <div data-testid="dialog-esc-content">"Press Escape to close"</div>
/// </DialogContent>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```
///
/// ## Focus trap
/// Tab cycles within the dialog so keyboard users cannot accidentally tab into the obscured page behind the mask.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="dialog-focus">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open dialog"</Button>
/// <Dialog open=open>
/// <DialogSurface>
/// <DialogBody>
/// <DialogContent>
/// <div data-testid="dialog-focus-first">
/// <Button>"First"</Button>
/// </div>
/// <div data-testid="dialog-focus-last">
/// <Button>"Last"</Button>
/// </div>
/// </DialogContent>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```
///
/// ## Theme token
/// Dialog surfaces inherit neutral background tokens from the Orbital theme provider.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// let open = RwSignal::new(false);
/// view! {
/// <div data-testid="dialog-theme">
/// <Button on_click=Callback::new(move |_| open.set(true))>"Open dialog"</Button>
/// <Dialog open=open>
/// <DialogSurface>
/// <DialogBody>
/// <DialogContent>"Themed dialog surface"</DialogContent>
/// </DialogBody>
/// </DialogSurface>
/// </Dialog>
/// </div>
/// }
/// ```