use dioxus::prelude::*;
use crate::icon::{Icon, IconKind, RenderSvgIcon, SvgIcon};
use crate::stepper::{StepperCtx, StepperOrientation};
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum StepState {
#[default]
Upcoming,
Active,
Completed,
Error,
}
impl StepState {
#[must_use]
pub fn as_class(&self) -> &'static str {
match self {
StepState::Upcoming => "sd-stepper-step--upcoming",
StepState::Active => "sd-stepper-step--active",
StepState::Completed => "sd-stepper-step--completed",
StepState::Error => "sd-stepper-step--error",
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct StepperStepProps {
#[props(default)]
pub step: usize,
pub label: String,
#[props(default)]
pub description: Option<String>,
#[props(default)]
pub icon: Option<SvgIcon>,
#[props(default)]
pub completed_icon: Option<SvgIcon>,
#[props(default)]
pub state: Option<StepState>,
#[props(default)]
pub disabled: bool,
#[props(default)]
pub class: String,
#[props(default)]
pub children: Option<Element>,
}
#[component]
pub fn StepperStep(props: StepperStepProps) -> Element {
let ctx_sig = try_use_context::<Signal<StepperCtx>>();
let active_idx = ctx_sig.as_ref().map(|c| c.read().active).unwrap_or(0);
let orientation = ctx_sig.as_ref().map(|c| c.read().orientation).unwrap_or(StepperOrientation::Horizontal);
let allow_click = ctx_sig.as_ref().map(|c| c.read().allow_click_completed).unwrap_or(true);
let current_state = props.state.unwrap_or(if props.step < active_idx {
StepState::Completed
} else if props.step == active_idx {
StepState::Active
} else {
StepState::Upcoming
});
let is_completed = current_state == StepState::Completed;
let is_active = current_state == StepState::Active;
let is_clickable = !props.disabled && ((is_completed && allow_click) || is_active);
#[cfg(feature = "theme")]
let theme_val = crate::theme::use_theme()();
#[cfg(feature = "theme")]
let base_override = theme_val.resolve_component_base("stepper_step").unwrap_or("");
#[cfg(feature = "theme")]
let class_modifiers = theme_val.resolve_class_modifiers("stepper_step", &props.class);
#[cfg(not(feature = "theme"))]
let base_override = "";
#[cfg(not(feature = "theme"))]
let class_modifiers = String::new();
let orientation_class = orientation.as_class();
let state_class = current_state.as_class();
let clickable_class = if is_clickable { "sd-stepper-step--clickable" } else { "" };
let disabled_class = if props.disabled { "sd-stepper-step--disabled" } else { "" };
let circle_content = match current_state {
StepState::Completed => {
if let Some(ref c_icon) = props.completed_icon {
rsx! { RenderSvgIcon { icon: c_icon.clone(), size: 16 } }
} else {
rsx! { Icon { kind: IconKind::Check, size: 16 } }
}
}
StepState::Error => {
rsx! { Icon { kind: IconKind::AlertCircle, size: 16 } }
}
StepState::Active | StepState::Upcoming => {
if let Some(ref ic) = props.icon {
rsx! { RenderSvgIcon { icon: ic.clone(), size: 16 } }
} else {
let num = props.step + 1;
rsx! { span { class: "sd-stepper-step__number", "{num}" } }
}
}
};
let step_idx = props.step;
let on_click_step = move |_| {
if is_clickable {
if let Some(ref c) = ctx_sig {
if let Some(ref handler) = c.read().on_change {
handler.call(step_idx);
}
}
}
};
rsx! {
div {
class: "sd-stepper-step {orientation_class} {state_class} {clickable_class} {disabled_class} {base_override} {class_modifiers} {props.class}",
button {
r#type: "button",
class: "sd-stepper-step__header",
disabled: !is_clickable,
"aria-current": if is_active { "step" } else { "" },
onclick: on_click_step,
div { class: "sd-stepper-step__circle",
{circle_content}
}
div { class: "sd-stepper-step__label-container",
span { class: "sd-stepper-step__label", "{props.label}" }
if let Some(ref desc) = props.description {
span { class: "sd-stepper-step__description", "{desc}" }
}
}
}
div { class: "sd-stepper-step__line", "aria-hidden": "true" }
if is_active {
if let Some(ref content) = props.children {
div { class: "sd-stepper-step__content",
{content}
}
}
}
}
}
}