dioxus_ui_system/organisms/
confirmation_dialog.rs1use crate::molecules::Dialog;
6use crate::theme::use_theme;
7use dioxus::prelude::*;
8
9#[derive(Props, Clone, PartialEq)]
11pub struct ConfirmationDialogProps {
12 pub open: bool,
14 pub on_close: EventHandler<()>,
16 pub title: String,
18 pub message: String,
20 #[props(default)]
22 pub confirm_text: Option<String>,
23 #[props(default)]
25 pub cancel_text: Option<String>,
26 #[props(default = ConfirmVariant::Danger)]
28 pub variant: ConfirmVariant,
29 #[props(default)]
31 pub icon: Option<String>,
32 pub on_confirm: EventHandler<()>,
34 #[props(default = false)]
36 pub loading: bool,
37 #[props(default)]
39 pub class: Option<String>,
40}
41
42#[derive(Default, Clone, PartialEq, Debug)]
44pub enum ConfirmVariant {
45 #[default]
46 Danger,
47 Warning,
48 Info,
49 Success,
50}
51
52impl ConfirmVariant {
53 fn colors(&self) -> (&'static str, &'static str) {
54 match self {
55 ConfirmVariant::Danger => ("#ef4444", "#dc2626"),
56 ConfirmVariant::Warning => ("#f59e0b", "#d97706"),
57 ConfirmVariant::Info => ("#3b82f6", "#2563eb"),
58 ConfirmVariant::Success => ("#22c55e", "#16a34a"),
59 }
60 }
61
62 fn default_icon(&self) -> &'static str {
63 match self {
64 ConfirmVariant::Danger => "đī¸",
65 ConfirmVariant::Warning => "â ī¸",
66 ConfirmVariant::Info => "âšī¸",
67 ConfirmVariant::Success => "â",
68 }
69 }
70}
71
72#[component]
74pub fn ConfirmationDialog(props: ConfirmationDialogProps) -> Element {
75 let theme = use_theme();
76
77 let class_css = props
78 .class
79 .as_ref()
80 .map(|c| format!(" {}", c))
81 .unwrap_or_default();
82
83 let (bg_color, _hover_color) = props.variant.colors();
84 let icon = props
85 .icon
86 .unwrap_or_else(|| props.variant.default_icon().to_string());
87
88 rsx! {
89 Dialog {
90 open: props.open,
91 on_close: props.on_close.clone(),
92 title: props.title.clone(),
93
94 div {
95 class: "confirmation-dialog{class_css}",
96 style: "text-align: center; padding: 24px 0;",
97
98 div {
100 class: "confirmation-dialog-icon",
101 style: "width: 64px; height: 64px; margin: 0 auto 24px; border-radius: 50%; background: {bg_color}20; display: flex; align-items: center; justify-content: center; font-size: 32px;",
102 "{icon}"
103 }
104
105 p {
107 class: "confirmation-dialog-message",
108 style: "margin: 0 0 32px 0; font-size: 16px; line-height: 1.6; color: {theme.tokens.read().colors.foreground.to_rgba()};",
109 "{props.message}"
110 }
111
112 div {
114 class: "confirmation-dialog-actions",
115 style: "display: flex; gap: 12px; justify-content: center;",
116
117 button {
118 type: "button",
119 class: "confirmation-dialog-cancel",
120 style: "padding: 12px 24px; font-size: 14px; font-weight: 500; color: {theme.tokens.read().colors.foreground.to_rgba()}; background: white; border: 1px solid {theme.tokens.read().colors.border.to_rgba()}; border-radius: 8px; cursor: pointer; transition: background 0.15s ease;",
121 onclick: move |_| props.on_close.call(()),
122 "{props.cancel_text.clone().unwrap_or_else(|| \"Cancel\".to_string())}"
123 }
124
125 button {
126 type: "button",
127 class: "confirmation-dialog-confirm",
128 style: format!("padding: 12px 24px; font-size: 14px; font-weight: 500; color: white; background: {bg_color}; border: none; border-radius: 8px; cursor: pointer; transition: background 0.15s ease; opacity: {};", if props.loading { "0.7" } else { "1" }),
129 disabled: props.loading,
130 onclick: move |_| props.on_confirm.call(()),
131
132 if props.loading {
133 span {
134 style: "display: inline-block; margin-right: 8px; animation: spin 1s linear infinite;",
135 "âŗ"
136 }
137 }
138
139 "{props.confirm_text.clone().unwrap_or_else(|| \"Confirm\".to_string())}"
140 }
141 }
142 }
143 }
144
145
146 }
147}
148
149#[derive(Props, Clone, PartialEq)]
151pub struct DeleteConfirmDialogProps {
152 pub open: bool,
153 pub on_close: EventHandler<()>,
154 #[props(default)]
155 pub title: Option<String>,
156 pub item_name: String,
157 pub on_confirm: EventHandler<()>,
158 #[props(default = false)]
159 pub loading: bool,
160}
161
162#[component]
164pub fn DeleteConfirmDialog(props: DeleteConfirmDialogProps) -> Element {
165 rsx! {
166 ConfirmationDialog {
167 open: props.open,
168 on_close: props.on_close,
169 title: props.title.unwrap_or_else(|| "Delete item".to_string()),
170 message: format!("Are you sure you want to delete \"{}\"? This action cannot be undone.", props.item_name),
171 confirm_text: Some("Delete".to_string()),
172 cancel_text: Some("Keep".to_string()),
173 variant: ConfirmVariant::Danger,
174 icon: Some("đī¸".to_string()),
175 on_confirm: props.on_confirm,
176 loading: props.loading,
177 }
178 }
179}
180
181#[derive(Props, Clone, PartialEq)]
183pub struct UnsavedChangesDialogProps {
184 pub open: bool,
185 pub on_close: EventHandler<()>,
186 pub on_save: EventHandler<()>,
187 pub on_discard: EventHandler<()>,
188}
189
190#[component]
192pub fn UnsavedChangesDialog(props: UnsavedChangesDialogProps) -> Element {
193 let theme = use_theme();
194
195 rsx! {
196 Dialog {
197 open: props.open,
198 on_close: props.on_close,
199 title: "Unsaved changes",
200
201 div {
202 style: "text-align: center; padding: 24px 0;",
203
204 div {
205 style: "width: 64px; height: 64px; margin: 0 auto 24px; border-radius: 50%; background: #f59e0b20; display: flex; align-items: center; justify-content: center; font-size: 32px;",
206 "â ī¸"
207 }
208
209 p {
210 style: "margin: 0 0 32px 0; font-size: 16px; line-height: 1.6; color: {theme.tokens.read().colors.foreground.to_rgba()};",
211 "You have unsaved changes. What would you like to do?"
212 }
213
214 div {
215 style: "display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;",
216
217 button {
218 type: "button",
219 style: "padding: 12px 24px; font-size: 14px; font-weight: 500; color: {theme.tokens.read().colors.foreground.to_rgba()}; background: white; border: 1px solid {theme.tokens.read().colors.border.to_rgba()}; border-radius: 8px; cursor: pointer;",
220 onclick: move |_| props.on_close.call(()),
221 "Keep editing"
222 }
223
224 button {
225 type: "button",
226 style: "padding: 12px 24px; font-size: 14px; font-weight: 500; color: white; background: #ef4444; border: none; border-radius: 8px; cursor: pointer;",
227 onclick: move |_| props.on_discard.call(()),
228 "Discard changes"
229 }
230
231 button {
232 type: "button",
233 style: "padding: 12px 24px; font-size: 14px; font-weight: 500; color: white; background: {theme.tokens.read().colors.primary.to_rgba()}; border: none; border-radius: 8px; cursor: pointer;",
234 onclick: move |_| props.on_save.call(()),
235 "Save changes"
236 }
237 }
238 }
239 }
240 }
241}
242
243#[derive(Props, Clone, PartialEq)]
245pub struct SignOutDialogProps {
246 pub open: bool,
247 pub on_close: EventHandler<()>,
248 pub on_confirm: EventHandler<()>,
249}
250
251#[component]
253pub fn SignOutDialog(props: SignOutDialogProps) -> Element {
254 rsx! {
255 ConfirmationDialog {
256 open: props.open,
257 on_close: props.on_close,
258 title: "Sign out",
259 message: "Are you sure you want to sign out?",
260 confirm_text: "Sign out",
261 cancel_text: "Stay signed in",
262 variant: ConfirmVariant::Info,
263 icon: "đ",
264 on_confirm: props.on_confirm,
265 }
266 }
267}