use dioxus::prelude::*;
#[derive(Clone, PartialEq)]
pub enum RateSize {
Large,
Default,
Small,
}
impl RateSize {
pub fn as_class(&self) -> &'static str {
match self {
RateSize::Large => "el-rate--large",
RateSize::Default => "",
RateSize::Small => "el-rate--small",
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct RateProps {
#[props(default = 0.0)]
pub model_value: f64,
#[props(default = 5)]
pub max: u32,
#[props(default = false)]
pub disabled: bool,
#[props(default = false)]
pub allow_half: bool,
#[props(default = false)]
pub clearable: bool,
#[props(default = false)]
pub show_text: bool,
#[props(default = false)]
pub show_score: bool,
#[props(default)]
pub texts: Vec<String>,
#[props(default = "#F7BA2A".to_string())]
pub active_color: String,
#[props(default = "#C6D1DE".to_string())]
pub inactive_color: String,
#[props(default = RateSize::Default)]
pub size: RateSize,
#[props(default)]
pub on_change: Option<EventHandler<f64>>,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
}
#[component]
pub fn Rate(props: RateProps) -> Element {
let mut class_names = vec!["el-rate".to_string()];
let size_class = props.size.as_class();
if !size_class.is_empty() {
class_names.push(size_class.to_string());
}
if props.disabled {
class_names.push("is-disabled".to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.clone());
}
let class_string = class_names.join(" ");
let style_string = props.style.clone().unwrap_or_default();
let current_value = props.model_value;
let text_index = if current_value > 0.0 {
(current_value.ceil() as usize).saturating_sub(1).min(props.texts.len().saturating_sub(1))
} else {
0
};
let display_text = if props.show_text && !props.texts.is_empty() {
props.texts.get(text_index).cloned().unwrap_or_default()
} else if props.show_score {
format!("{}", current_value)
} else {
String::new()
};
let stars: Vec<(f64, String, &'static str)> = (1..=props.max)
.map(|i| {
let star_value = i as f64;
let is_active = star_value <= current_value;
let is_half = props.allow_half && star_value - 0.5 == current_value;
let color = if is_active { props.active_color.clone() } else { props.inactive_color.clone() };
let star_char = if is_active { "★" } else if is_half { "⯨" } else { "☆" };
(star_value, color, star_char)
})
.collect();
rsx! {
div {
class: "{class_string}",
style: "{style_string}",
role: "slider",
aria_valuenow: "{current_value}",
aria_valuemax: "{props.max}",
for (star_value, color, star_char) in stars.into_iter() {
span {
class: "el-rate__item",
style: "cursor: pointer;",
onclick: move |_| {
if !props.disabled {
if props.clearable && star_value == current_value {
if let Some(handler) = props.on_change {
handler.call(0.0);
}
} else {
if let Some(handler) = props.on_change {
handler.call(star_value);
}
}
}
},
i {
class: "el-rate__icon",
style: "color: {color};",
"{star_char}"
}
}
}
if props.show_text || props.show_score {
span {
class: "el-rate__text",
"{display_text}"
}
}
}
}
}