radix_leptos_primitives/components/
alert_dialog.rs

1use crate::utils::{merge_classes, generate_id};
2use leptos::callback::Callback;
3use leptos::children::Children;
4use leptos::prelude::*;
5
6/// AlertDialog component - Modal alert dialogs for user confirmations
7///
8/// The AlertDialog component provides accessible modal dialogs for critical user actions
9/// like confirmations, warnings, and important information display.
10///
11/// # Features
12/// - Accessible modal dialog with proper ARIA attributes
13/// - Focus management and keyboard navigation
14/// - Multiple variants (default, destructive, warning)
15/// - Customizable actions and content
16/// - Backdrop click handling
17/// - Escape key handling
18///
19/// # Example
20///
21/// ```rust,no_run
22/// use leptos::prelude::*;
23/// use radix_leptos_primitives::*;
24///
25/// #[component]
26/// fn MyComponent() -> impl IntoView {
27///     let (show_dialog, set_show_dialog) = create_signal(false);
28///
29///     view! {
30///         <Button on_click=move |_| set_show_dialog.set(true)>
31///             "Delete Item"
32///         </Button>
33///         
34///         <AlertDialog
35///             open=show_dialog
36///             onopen_change=move |open| set_show_dialog.set(open)
37///             variant=AlertDialogVariant::Destructive
38///         >
39///             <AlertDialogTitle>"Delete Item"</AlertDialogTitle>
40///             <AlertDialogDescription>
41///                 "Are you sure you want to delete this item? This action cannot be undone."
42///             </AlertDialogDescription>
43///             <AlertDialogFooter>
44///                 <Button variant=ButtonVariant::Outline on_click=move |_| set_show_dialog.set(false)>
45///                     "Cancel"
46///                 </Button>
47///                 <Button variant=ButtonVariant::Destructive on_click=move |_| {
48///                     // Delete logic here
49///                     set_show_dialog.set(false);
50///                 }>
51///                     "Delete"
52///                 </Button>
53///             </AlertDialogFooter>
54///         </AlertDialog>
55///     }
56/// }
57/// ```
58
59#[derive(Debug, Clone, Copy, PartialEq)]
60pub enum AlertDialogVariant {
61    Default,
62    Destructive,
63    Warning,
64}
65
66impl AlertDialogVariant {
67    pub fn as_str(&self) -> &'static str {
68        match self {
69            AlertDialogVariant::Default => "default",
70            AlertDialogVariant::Destructive => "destructive",
71            AlertDialogVariant::Warning => "warning",
72        }
73    }
74}
75
76/// AlertDialog root component
77#[component]
78pub fn AlertDialog(
79    #[prop(optional)] class: Option<String>,
80    #[prop(optional)] style: Option<String>,
81    #[prop(optional)] children: Option<Children>,
82    #[prop(optional)] open: Option<bool>,
83    #[prop(optional)] variant: Option<AlertDialogVariant>,
84    #[prop(optional)] onopen_change: Option<Callback<bool>>,
85) -> impl IntoView {
86    let open = open.unwrap_or(false);
87    let variant = variant.unwrap_or(AlertDialogVariant::Default);
88    let onopen_change = onopen_change.unwrap_or_else(|| Callback::new(|_| {}));
89
90    let class = merge_classes(vec!["alert-dialog", variant.as_str()]);
91}
92
93/// AlertDialog title component
94#[component]
95pub fn AlertDialogTitle(
96    #[prop(optional)] class: Option<String>,
97    #[prop(optional)] style: Option<String>,
98    #[prop(optional)] children: Option<Children>,
99) -> impl IntoView {
100    let class = merge_classes(vec!["alert-dialog-title", class.as_deref().unwrap_or("")]);
101
102    view! {
103        <h2
104            id="alert-dialog-title"
105            class=class
106            style=style
107        >
108            {children.map(|c| c())}
109        </h2>
110    }
111}
112
113/// AlertDialog description component
114#[component]
115pub fn AlertDialogDescription(
116    #[prop(optional)] class: Option<String>,
117    #[prop(optional)] style: Option<String>,
118    #[prop(optional)] children: Option<Children>,
119) -> impl IntoView {
120    let class =
121        merge_classes(vec!["alert-dialog-description", class.as_deref().unwrap_or("")]);
122
123    view! {
124        <p
125            id="alert-dialog-description"
126            class=class
127            style=style
128        >
129            {children.map(|c| c())}
130        </p>
131    }
132}
133
134/// AlertDialog footer component
135#[component]
136pub fn AlertDialogFooter(
137    #[prop(optional)] class: Option<String>,
138    #[prop(optional)] style: Option<String>,
139    #[prop(optional)] children: Option<Children>,
140) -> impl IntoView {
141    let class = merge_classes(vec!["alert-dialog-footer", class.as_deref().unwrap_or("")]);
142
143    view! {
144        <div
145            class=class
146            style=style
147        >
148            {children.map(|c| c())}
149        </div>
150    }
151}
152
153/// AlertDialog action component
154#[component]
155pub fn AlertDialogAction(
156    #[prop(optional)] class: Option<String>,
157    #[prop(optional)] style: Option<String>,
158    #[prop(optional)] children: Option<Children>,
159    #[prop(optional)] on_click: Option<Callback<()>>,
160) -> impl IntoView {
161    let on_click = on_click.unwrap_or_else(|| Callback::new(|_| {}));
162
163    let class = merge_classes(vec!["alert-dialog-action", class.as_deref().unwrap_or("")]);
164
165    view! {
166        <button
167            class=class
168            style=style
169            on:click=move |_| on_click.run(())
170        >
171            {children.map(|c| c())}
172        </button>
173    }
174}
175
176/// AlertDialog cancel component
177#[component]
178pub fn AlertDialogCancel(
179    #[prop(optional)] class: Option<String>,
180    #[prop(optional)] style: Option<String>,
181    #[prop(optional)] children: Option<Children>,
182    #[prop(optional)] on_click: Option<Callback<()>>,
183) -> impl IntoView {
184    let on_click = on_click.unwrap_or_else(|| Callback::new(|_| {}));
185
186    let class = merge_classes(vec!["alert-dialog-cancel", class.as_deref().unwrap_or("")]);
187
188    view! {
189        <button
190            class=class
191            style=style
192            on:click=move |_| on_click.run(())
193        >
194            {children.map(|c| c())}
195        </button>
196    }
197}
198
199// Helper function to merge CSS classes
200
201#[cfg(test)]
202mod tests {
203    use proptest::prelude::*;
204
205    #[test]
206    fn test_alert_dialog_component_creation() {}
207
208    #[test]
209    fn test_alert_dialog_with_variant_component_creation() {}
210
211    proptest! {
212        #[test]
213        fn test_alert_dialog_props(___class in ".*", ___style in ".*") {
214
215        }
216
217        #[test]
218        fn test_alert_dialogopen_state(__open: bool, ___variant_index in 0..3usize) {
219
220        }
221
222        #[test]
223        fn test_alert_dialog_variants(___variant_index in 0..3usize) {
224
225        }
226
227        #[test]
228        fn test_alert_dialog_title_props(___class in ".*", ___style in ".*") {
229
230        }
231
232        #[test]
233        fn test_alert_dialog_description_props(___class in ".*", ___style in ".*") {
234
235        }
236
237        #[test]
238        fn test_alert_dialog_footer_props(___class in ".*", ___style in ".*") {
239
240        }
241
242        #[test]
243        fn test_alert_dialog_action_props(___class in ".*", ___style in ".*") {
244
245        }
246
247        #[test]
248        fn test_alert_dialog_cancel_props(___class in ".*", ___style in ".*") {
249
250        }
251    }
252}