fix: even more closing of M0/M1/M2 gaps against the spec
This commit is contained in:
252
crates/bds-ui/src/views/modal.rs
Normal file
252
crates/bds-ui/src/views/modal.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use iced::widget::{button, column, container, row, text, Space};
|
||||
use iced::widget::text::Shaping;
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::i18n::t;
|
||||
|
||||
/// Active modal state. Only one modal at a time.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ModalState {
|
||||
/// Per modals.allium ConfirmDeleteModal: warning, entity name, references.
|
||||
ConfirmDelete {
|
||||
entity_name: String,
|
||||
references: Vec<String>,
|
||||
on_confirm: ConfirmAction,
|
||||
},
|
||||
/// Per modals.allium ConfirmDialog: generic confirmation.
|
||||
Confirm {
|
||||
title: String,
|
||||
message: String,
|
||||
on_confirm: ConfirmAction,
|
||||
},
|
||||
}
|
||||
|
||||
/// What action to perform when modal is confirmed.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConfirmAction {
|
||||
DeleteProject(String),
|
||||
DeletePost(String),
|
||||
DeleteMedia(String),
|
||||
DeleteScript(String),
|
||||
DeleteTemplate(String),
|
||||
MergeTags { source: String, target: String },
|
||||
}
|
||||
|
||||
// ── Modal backdrop style ──
|
||||
|
||||
fn backdrop_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgba(0.0, 0.0, 0.0, 0.5))),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn modal_box_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.18, 0.18, 0.22))),
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.30, 0.30, 0.35),
|
||||
width: 1.0,
|
||||
radius: 8.0.into(),
|
||||
},
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_button_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.28, 0.28, 0.33),
|
||||
_ => Color::from_rgb(0.22, 0.22, 0.27),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb(0.80, 0.80, 0.85),
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.35, 0.35, 0.40),
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn danger_button_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.85, 0.20, 0.20),
|
||||
_ => Color::from_rgb(0.75, 0.15, 0.15),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_button_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.20, 0.55, 0.85),
|
||||
_ => Color::from_rgb(0.15, 0.45, 0.75),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the modal overlay.
|
||||
pub fn view(state: &ModalState, locale: UiLocale) -> Element<'static, Message> {
|
||||
let modal_content: Element<'static, Message> = match state {
|
||||
ModalState::ConfirmDelete {
|
||||
entity_name,
|
||||
references,
|
||||
on_confirm,
|
||||
} => {
|
||||
let title = text(t(locale, "modal.confirmDelete.title"))
|
||||
.size(16)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::WHITE);
|
||||
|
||||
let warning_icon = text("\u{26A0}")
|
||||
.size(20)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(1.0, 0.80, 0.0));
|
||||
|
||||
let entity_label = text(entity_name.clone())
|
||||
.size(14)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(0.90, 0.90, 0.95));
|
||||
|
||||
let warning_text = text(t(locale, "modal.confirmDelete.warning"))
|
||||
.size(12)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(0.70, 0.70, 0.75));
|
||||
|
||||
let mut content_col = column![
|
||||
row![warning_icon, Space::with_width(8.0), title]
|
||||
.align_y(Alignment::Center),
|
||||
Space::with_height(12.0),
|
||||
entity_label,
|
||||
warning_text,
|
||||
]
|
||||
.spacing(4);
|
||||
|
||||
// Reference section
|
||||
if !references.is_empty() {
|
||||
content_col = content_col.push(Space::with_height(8.0));
|
||||
for r in references {
|
||||
content_col = content_col.push(
|
||||
text(format!("\u{2022} {r}"))
|
||||
.size(11)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(0.60, 0.60, 0.65)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let on_confirm_clone = on_confirm.clone();
|
||||
let buttons = row![
|
||||
button(
|
||||
text(t(locale, "modal.confirmDelete.cancel"))
|
||||
.size(13)
|
||||
.shaping(Shaping::Advanced)
|
||||
)
|
||||
.on_press(Message::DismissModal)
|
||||
.padding([6, 16])
|
||||
.style(cancel_button_style),
|
||||
Space::with_width(Length::Fill),
|
||||
button(
|
||||
text(t(locale, "modal.confirmDelete.delete"))
|
||||
.size(13)
|
||||
.shaping(Shaping::Advanced)
|
||||
)
|
||||
.on_press(Message::ConfirmModal(on_confirm_clone))
|
||||
.padding([6, 16])
|
||||
.style(danger_button_style),
|
||||
];
|
||||
|
||||
content_col = content_col.push(Space::with_height(16.0));
|
||||
content_col = content_col.push(buttons);
|
||||
|
||||
container(content_col.padding(20))
|
||||
.width(Length::Fixed(380.0))
|
||||
.style(modal_box_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
ModalState::Confirm {
|
||||
title: dialog_title,
|
||||
message: dialog_message,
|
||||
on_confirm,
|
||||
} => {
|
||||
let title = text(dialog_title.clone())
|
||||
.size(16)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::WHITE);
|
||||
|
||||
let msg = text(dialog_message.clone())
|
||||
.size(13)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(0.80, 0.80, 0.85));
|
||||
|
||||
let on_confirm_clone = on_confirm.clone();
|
||||
let buttons = row![
|
||||
button(
|
||||
text(t(locale, "modal.confirm.cancel"))
|
||||
.size(13)
|
||||
.shaping(Shaping::Advanced)
|
||||
)
|
||||
.on_press(Message::DismissModal)
|
||||
.padding([6, 16])
|
||||
.style(cancel_button_style),
|
||||
Space::with_width(Length::Fill),
|
||||
button(
|
||||
text(t(locale, "modal.confirm.confirm"))
|
||||
.size(13)
|
||||
.shaping(Shaping::Advanced)
|
||||
)
|
||||
.on_press(Message::ConfirmModal(on_confirm_clone))
|
||||
.padding([6, 16])
|
||||
.style(confirm_button_style),
|
||||
];
|
||||
|
||||
let content_col = column![
|
||||
title,
|
||||
Space::with_height(12.0),
|
||||
msg,
|
||||
Space::with_height(16.0),
|
||||
buttons,
|
||||
]
|
||||
.spacing(4);
|
||||
|
||||
container(content_col.padding(20))
|
||||
.width(Length::Fixed(380.0))
|
||||
.style(modal_box_style)
|
||||
.into()
|
||||
}
|
||||
};
|
||||
|
||||
// Center the modal in a full-screen backdrop
|
||||
container(
|
||||
container(modal_content)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(backdrop_style)
|
||||
.into()
|
||||
}
|
||||
Reference in New Issue
Block a user