use leptos::wasm_bindgen::JsCast;
use leptos::web_sys::HtmlInputElement;
use leptos::{ev, prelude::*};
use orbital_base_components::{
new_field_id, FieldInjection, OptionBind, RatingColor, RatingRule, RatingRuleTrigger,
RatingSize, Rule,
};
use orbital_macros::component_doc;
use orbital_style::inject_style;
use super::injection::RatingInjection;
use super::rating_item::RatingItem;
use super::styles::rating_styles;
#[component_doc(
category = "Data Display",
preview_slug = "rating",
preview_label = "Rating",
preview_icon = icondata::AiStarOutlined,
)]
#[component]
pub fn Rating(
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
rules: Vec<RatingRule>,
#[prop(optional, into)]
name: MaybeProp<String>,
#[prop(optional, into)]
value: OptionBind<f32>,
#[prop(default = 5.into(), into)]
max: Signal<u8>,
#[prop(default = 1.0.into(), into)]
step: Signal<f32>,
#[prop(default = RatingSize::ExtraLarge.into(), into)]
size: Signal<RatingSize>,
#[prop(optional, into)]
color: Signal<RatingColor>,
) -> impl IntoView {
inject_style("orbital-rating", rating_styles());
let (id, name) = FieldInjection::use_id_and_name(id, name);
let value = StoredValue::new(value);
let validate = Rule::validate(rules, value, name);
let fallback_name = StoredValue::new(new_field_id());
let resolved_name =
Signal::derive(move || name.get().unwrap_or_else(|| fallback_name.get_value()));
let hovered_value = RwSignal::new(None::<f32>);
let on_change = move |ev: ev::Event| {
let Some(target) = ev.target() else {
return;
};
let Ok(el) = target.dyn_into::<HtmlInputElement>() else {
return;
};
if el.type_() != "radio" || el.name() != resolved_name.get() {
return;
}
if let Ok(new_value) = el.value().parse::<f32>() {
value.with_value(|value| value.set(Some(new_value)));
validate.run(Some(RatingRuleTrigger::Change));
}
};
let on_mouseover = move |ev: ev::MouseEvent| {
let Some(target) = ev.target() else {
return;
};
let Ok(el) = target.dyn_into::<HtmlInputElement>() else {
return;
};
if el.type_() != "radio" || el.name() != resolved_name.get() {
return;
}
if let Ok(new_value) = el.value().parse::<f32>() {
hovered_value.set(Some(new_value));
}
};
let on_mouseleave = move |_| {
hovered_value.set(None);
};
let class = MaybeProp::derive(move || {
let mut parts = vec!["orbital-rating".to_string()];
if let Some(extra) = class.get() {
if !extra.is_empty() {
parts.push(extra);
}
}
Some(parts.join(" "))
});
view! {
<div
role="radiogroup"
class=class
id=id
on:change=on_change
on:mouseover=on_mouseover
on:mouseleave=on_mouseleave
>
<leptos::context::Provider value=RatingInjection {
value: value.get_value(),
hovered_value,
name: resolved_name,
step,
size,
color,
interactive: true,
}>
{move || {
let mut max = max.get();
if max < 2 {
max = 2;
}
(0..max)
.map(|i| view! { <RatingItem value=i + 1 /> })
.collect_view()
}}
</leptos::context::Provider>
</div>
}
}