Skip to main content

orbital_base_components/rating/
item.rs

1use 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
41/// Fraction of a star filled (0.0, 0.5, or 1.0) for a given item index.
42pub 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}