use crate::accessibility;
use crate::prop_value::PropValue;
use crate::surface::SurfaceNode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToastType {
Info,
Success,
Warning,
Error,
}
impl ToastType {
fn as_str(self) -> &'static str {
match self {
Self::Info => "info",
Self::Success => "success",
Self::Warning => "warning",
Self::Error => "error",
}
}
}
pub struct ModalBuilder {
visible: bool,
on_dismiss: PropValue,
title: Option<String>,
children: Vec<SurfaceNode>,
}
impl ModalBuilder {
pub fn new(visible: bool, on_dismiss: PropValue) -> Self {
Self {
visible,
on_dismiss,
title: None,
children: Vec::new(),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn child(mut self, child: SurfaceNode) -> Self {
self.children.push(child);
self
}
pub fn build(self) -> SurfaceNode {
let mut node = SurfaceNode::new("Modal");
node.set_prop("visible", PropValue::Bool(self.visible));
node.set_prop("on_dismiss", self.on_dismiss);
if let Some(title) = self.title {
node.set_prop("title", PropValue::String(title));
}
for child in self.children {
node.add_child(child);
}
accessibility::ensure_accessible(&mut node);
node
}
}
pub struct ToastBuilder {
message: String,
duration: Option<f64>,
toast_type: Option<ToastType>,
}
impl ToastBuilder {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
duration: None,
toast_type: None,
}
}
pub fn duration(mut self, duration: f64) -> Self {
self.duration = Some(duration);
self
}
pub fn toast_type(mut self, toast_type: ToastType) -> Self {
self.toast_type = Some(toast_type);
self
}
pub fn build(self) -> SurfaceNode {
let mut node = SurfaceNode::new("Toast");
node.set_prop("message", PropValue::String(self.message));
if let Some(duration) = self.duration {
node.set_prop("duration", PropValue::Number(duration));
}
if let Some(toast_type) = self.toast_type {
node.set_prop("type", PropValue::String(toast_type.as_str().to_string()));
}
accessibility::ensure_accessible(&mut node);
node
}
}
pub fn validate_feedback_node(node: &SurfaceNode) -> Vec<String> {
match node.component_type.as_str() {
"Modal" => validate_modal(node),
"Toast" => validate_toast(node),
_ => vec![format!(
"Unknown feedback component: {}",
node.component_type
)],
}
}
fn validate_modal(node: &SurfaceNode) -> Vec<String> {
let mut errors = Vec::new();
match node.props.get("visible") {
Some(PropValue::Bool(_)) => {}
Some(other) => errors.push(format!(
"Modal.visible: expected bool, got {}",
other.type_name()
)),
None => errors.push("Modal.visible: required prop missing".to_string()),
}
match node.props.get("on_dismiss") {
Some(PropValue::ActionRef { .. }) => {}
Some(other) => errors.push(format!(
"Modal.on_dismiss: expected action, got {}",
other.type_name()
)),
None => errors.push("Modal.on_dismiss: required prop missing".to_string()),
}
if let Some(prop) = node.props.get("title") {
if !matches!(prop, PropValue::String(_)) {
errors.push(format!(
"Modal.title: expected string, got {}",
prop.type_name()
));
}
}
if let Some(prop) = node.props.get("accessible") {
errors.extend(accessibility::validate_accessible_prop("Modal", prop));
}
for key in node.props.keys() {
if !matches!(
key.as_str(),
"visible" | "on_dismiss" | "title" | "accessible"
) {
errors.push(format!("Modal: unknown prop '{key}'"));
}
}
errors
}
fn validate_toast(node: &SurfaceNode) -> Vec<String> {
let mut errors = Vec::new();
match node.props.get("message") {
Some(PropValue::String(_)) => {}
Some(other) => errors.push(format!(
"Toast.message: expected string, got {}",
other.type_name()
)),
None => errors.push("Toast.message: required prop missing".to_string()),
}
if let Some(prop) = node.props.get("duration") {
if !matches!(prop, PropValue::Number(_)) {
errors.push(format!(
"Toast.duration: expected number, got {}",
prop.type_name()
));
}
}
if let Some(prop) = node.props.get("type") {
match prop {
PropValue::String(s)
if matches!(s.as_str(), "info" | "success" | "warning" | "error") => {}
_ => errors.push(format!(
"Toast.type: expected one of [info, success, warning, error], got {:?}",
prop
)),
}
}
if !node.children.is_empty() {
errors.push(format!(
"Toast: does not accept children, but got {}",
node.children.len()
));
}
if let Some(prop) = node.props.get("accessible") {
errors.extend(accessibility::validate_accessible_prop("Toast", prop));
}
for key in node.props.keys() {
if !matches!(key.as_str(), "message" | "duration" | "type" | "accessible") {
errors.push(format!("Toast: unknown prop '{key}'"));
}
}
errors
}