dioxus_element_plug/components/
rate.rs1use dioxus::prelude::*;
2
3#[derive(Clone, PartialEq)]
5pub enum RateSize {
6 Large,
7 Default,
8 Small,
9}
10
11impl RateSize {
12 pub fn as_class(&self) -> &'static str {
13 match self {
14 RateSize::Large => "el-rate--large",
15 RateSize::Default => "",
16 RateSize::Small => "el-rate--small",
17 }
18 }
19}
20
21#[derive(Props, Clone, PartialEq)]
23pub struct RateProps {
24 #[props(default = 0.0)]
26 pub model_value: f64,
27
28 #[props(default = 5)]
30 pub max: u32,
31
32 #[props(default = false)]
34 pub disabled: bool,
35
36 #[props(default = false)]
38 pub allow_half: bool,
39
40 #[props(default = false)]
42 pub clearable: bool,
43
44 #[props(default = false)]
46 pub show_text: bool,
47
48 #[props(default = false)]
50 pub show_score: bool,
51
52 #[props(default)]
54 pub texts: Vec<String>,
55
56 #[props(default = "#F7BA2A".to_string())]
58 pub active_color: String,
59
60 #[props(default = "#C6D1DE".to_string())]
62 pub inactive_color: String,
63
64 #[props(default = RateSize::Default)]
66 pub size: RateSize,
67
68 #[props(default)]
70 pub on_change: Option<EventHandler<f64>>,
71
72 #[props(default)]
74 pub class: Option<String>,
75
76 #[props(default)]
78 pub style: Option<String>,
79}
80
81#[component]
96pub fn Rate(props: RateProps) -> Element {
97 let mut class_names = vec!["el-rate".to_string()];
98
99 let size_class = props.size.as_class();
100 if !size_class.is_empty() {
101 class_names.push(size_class.to_string());
102 }
103
104 if props.disabled {
105 class_names.push("is-disabled".to_string());
106 }
107
108 if let Some(ref custom_class) = props.class {
109 class_names.push(custom_class.clone());
110 }
111
112 let class_string = class_names.join(" ");
113 let style_string = props.style.clone().unwrap_or_default();
114
115 let current_value = props.model_value;
116 let text_index = if current_value > 0.0 {
117 (current_value.ceil() as usize).saturating_sub(1).min(props.texts.len().saturating_sub(1))
118 } else {
119 0
120 };
121
122 let display_text = if props.show_text && !props.texts.is_empty() {
123 props.texts.get(text_index).cloned().unwrap_or_default()
124 } else if props.show_score {
125 format!("{}", current_value)
126 } else {
127 String::new()
128 };
129
130 let stars: Vec<(f64, String, &'static str)> = (1..=props.max)
131 .map(|i| {
132 let star_value = i as f64;
133 let is_active = star_value <= current_value;
134 let is_half = props.allow_half && star_value - 0.5 == current_value;
135 let color = if is_active { props.active_color.clone() } else { props.inactive_color.clone() };
136 let star_char = if is_active { "★" } else if is_half { "⯨" } else { "☆" };
137 (star_value, color, star_char)
138 })
139 .collect();
140
141 rsx! {
142 div {
143 class: "{class_string}",
144 style: "{style_string}",
145 role: "slider",
146 aria_valuenow: "{current_value}",
147 aria_valuemax: "{props.max}",
148 for (star_value, color, star_char) in stars.into_iter() {
149 span {
150 class: "el-rate__item",
151 style: "cursor: pointer;",
152 onclick: move |_| {
153 if !props.disabled {
154 if props.clearable && star_value == current_value {
155 if let Some(handler) = props.on_change {
156 handler.call(0.0);
157 }
158 } else {
159 if let Some(handler) = props.on_change {
160 handler.call(star_value);
161 }
162 }
163 }
164 },
165 i {
166 class: "el-rate__icon",
167 style: "color: {color};",
168 "{star_char}"
169 }
170 }
171 }
172 if props.show_text || props.show_score {
173 span {
174 class: "el-rate__text",
175 "{display_text}"
176 }
177 }
178 }
179 }
180}