use dioxus::prelude::*;
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BadgeType {
Default,
Success,
Error,
Warning,
Info,
}
impl BadgeType {
pub fn color_class(&self) -> &'static str {
match self {
BadgeType::Default => "bg-gray-500",
BadgeType::Success => "bg-green-500",
BadgeType::Error => "bg-red-500",
BadgeType::Warning => "bg-orange-500",
BadgeType::Info => "bg-blue-500",
}
}
pub fn text_color_class(&self) -> &'static str {
match self {
BadgeType::Default => "text-white",
BadgeType::Success => "text-white",
BadgeType::Error => "text-white",
BadgeType::Warning => "text-white",
BadgeType::Info => "text-white",
}
}
}
#[component]
pub fn Badge(
#[props(default)]
value: Option<String>,
#[props(default = BadgeType::Default)]
badge_type: BadgeType,
#[props(default = false)]
dot: bool,
#[props(default)]
max: Option<u32>,
#[props(default = false)]
show_zero: bool,
#[props(default = true)]
show: bool,
#[props(default = false)]
processing: bool,
#[props(default)]
color: Option<String>,
#[props(default)]
offset: Option<(String, String)>,
#[props(default)]
class: Option<String>,
children: Element,
) -> Element {
let display_value = if !show {
None
} else if let Some(val) = value.as_ref() {
if let Ok(num) = val.parse::<u32>() {
if num == 0 && !show_zero {
None
} else if let Some(max_val) = max {
if num > max_val {
Some(format!("{}+", max_val))
} else {
Some(val.clone())
}
} else {
Some(val.clone())
}
} else {
Some(val.clone())
}
} else {
None
};
let should_show = show && (display_value.is_some() || dot);
let text_class = if color.is_some() {
"text-white" } else {
badge_type.text_color_class()
};
let color_style = use_memo(move || {
if let Some(custom_color) = color.as_ref() {
format!("background-color: {};", custom_color)
} else {
match badge_type {
BadgeType::Default => "background-color: #6b7280;".to_string(),
BadgeType::Success => "background-color: #10b981;".to_string(),
BadgeType::Error => "background-color: #ef4444;".to_string(),
BadgeType::Warning => "background-color: #f59e0b;".to_string(),
BadgeType::Info => "background-color: #3b82f6;".to_string(),
}
}
});
let offset_style = use_memo(move || {
if let Some((left, top)) = &offset {
format!("left: {}; top: {};", left, top)
} else {
if dot {
"right: -4px; top: -4px;".to_string()
} else {
"right: -8px; top: -8px;".to_string()
}
}
});
let base_class = if dot {
"absolute w-2 h-2 rounded-full border-2 border-white"
} else {
"absolute min-w-[18px] h-[18px] px-1 rounded-full text-xs font-medium flex items-center justify-center border-2 border-white"
};
let final_class = if let Some(custom_class) = class {
format!("{} {} {}", base_class, text_class, custom_class)
} else {
format!("{} {}", base_class, text_class)
};
rsx! {
div {
class: "relative inline-block",
{children}
if should_show {
div {
class: final_class,
style: format!("{} {}", color_style.read(), offset_style.read()),
if processing && !dot {
div {
class: "absolute inset-0 rounded-full animate-spin",
style: "background: conic-gradient(from 0deg, transparent, rgba(255,255,255,0.8), rgba(255,255,255,0.4), transparent);",
}
div {
class: "absolute inset-0 rounded-full animate-pulse",
style: "background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, transparent 70%);",
}
}
if !dot {
if let Some(val) = &display_value {
"{val}"
}
}
}
}
}
}
}
pub fn create_badge(
value: Option<String>,
badge_type: BadgeType,
dot: bool,
) -> impl Fn(Element) -> Element {
move |children| {
rsx! {
Badge {
value: value.clone(),
badge_type,
dot,
children
}
}
}
}
pub fn create_number_badge(
value: u32,
badge_type: BadgeType,
max: Option<u32>,
show_zero: bool,
) -> impl Fn(Element) -> Element {
move |children| {
rsx! {
Badge {
value: Some(value.to_string()),
badge_type,
max,
show_zero,
children
}
}
}
}
pub fn create_dot_badge(badge_type: BadgeType, processing: bool) -> impl Fn(Element) -> Element {
move |children| {
rsx! {
Badge {
badge_type,
dot: true,
processing,
children
}
}
}
}
pub fn create_custom_badge(
content: String,
badge_type: BadgeType,
color: Option<String>,
) -> impl Fn(Element) -> Element {
move |children| {
rsx! {
Badge {
value: Some(content.clone()),
badge_type,
color: color.clone(),
children
}
}
}
}
pub fn create_controlled_badge(
value: u32,
show: bool,
badge_type: BadgeType,
) -> impl Fn(Element) -> Element {
move |children| {
rsx! {
Badge {
value: Some(value.to_string()),
show,
badge_type,
children
}
}
}
}