orbital_base_components/rating/
item.rs1use leptos::{either::Either, prelude::*};
2
3#[derive(Clone, Copy, Default, PartialEq, Eq)]
4pub enum RatingSize {
5 Small,
6 Medium,
7 Large,
8 #[default]
9 ExtraLarge,
10}
11
12impl RatingSize {
13 pub fn as_str(&self) -> &'static str {
14 match self {
15 Self::Small => "small",
16 Self::Medium => "medium",
17 Self::Large => "large",
18 Self::ExtraLarge => "extra-large",
19 }
20 }
21}
22
23#[derive(Clone, Copy, Default, PartialEq, Eq)]
24pub enum RatingColor {
25 #[default]
26 Brand,
27 Neutral,
28 Marigold,
29}
30
31impl RatingColor {
32 pub fn as_str(&self) -> &'static str {
33 match self {
34 Self::Brand => "brand",
35 Self::Neutral => "neutral",
36 Self::Marigold => "marigold",
37 }
38 }
39}
40
41pub fn filled_fraction(displayed_value: f32, item_value: u8, step: f32) -> f32 {
43 let value = f32::from(item_value);
44 let displayed = if step == 0.5 {
45 (displayed_value * 2.0).round() / 2.0
46 } else {
47 displayed_value.round()
48 };
49
50 if displayed >= value {
51 1.0
52 } else if step == 0.5 && displayed >= value - 0.5 {
53 0.5
54 } else {
55 0.0
56 }
57}
58
59#[component]
60pub fn BaseRatingItem(
61 value: u8,
62 #[prop(optional, into)] displayed_value: Signal<f32>,
63 #[prop(optional, into)] step: Signal<f32>,
64 #[prop(optional, into)] size: Signal<RatingSize>,
65 #[prop(optional, into)] color: Signal<RatingColor>,
66 #[prop(optional, into)] interactive: Signal<bool>,
67 #[prop(optional, into)] name: Signal<String>,
68) -> impl IntoView {
69 let icon_fill_width =
70 Memo::new(move |_| filled_fraction(displayed_value.get(), value, step.get()));
71 let half = Signal::derive(move || step.get() == 0.5);
72
73 view! {
74 <span
75 class=move || {
76 let mut parts = vec!["orbital-rating-item".to_string()];
77 if !interactive.get() {
78 parts.push("orbital-rating-item--filled".to_string());
79 }
80 parts.push(format!("orbital-rating-item--{}", color.get().as_str()));
81 parts.push(format!("orbital-rating-item--{}", size.get().as_str()));
82 parts.join(" ")
83 }
84 >
85 {move || {
86 if interactive.get() {
87 Either::Left(view! {
88 {half.get().then(|| view! {
89 <input
90 type="radio"
91 name=move || name.get()
92 aria-label=f32::from(value) - 0.5
93 class="orbital-rating-item__half-value-input"
94 value=f32::from(value) - 0.5
95 />
96 })}
97 <input
98 type="radio"
99 name=move || name.get()
100 aria-label=value
101 class="orbital-rating-item__full-value-input"
102 value=value
103 />
104 })
105 } else {
106 Either::Right(())
107 }
108 }}
109 <span
110 class="orbital-rating-item__icon"
111 style=move || format!("--orbital-rating-fill: {};", icon_fill_width.get())
112 />
113 </span>
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::filled_fraction;
120
121 #[test]
122 fn filled_fraction_whole_steps() {
123 assert_eq!(filled_fraction(3.0, 1, 1.0), 1.0);
124 assert_eq!(filled_fraction(3.0, 3, 1.0), 1.0);
125 assert_eq!(filled_fraction(3.0, 4, 1.0), 0.0);
126 }
127
128 #[test]
129 fn filled_fraction_half_steps() {
130 assert_eq!(filled_fraction(2.5, 3, 0.5), 0.5);
131 assert_eq!(filled_fraction(3.0, 3, 0.5), 1.0);
132 }
133}