Skip to main content

rgpui_component/stepper/
stepper.rs

1use std::rc::Rc;
2
3use rgpui::{
4    App, Axis, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
5    StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _,
6};
7
8use crate::{AxisExt, Sizable, Size, StyledExt as _, stepper::StepperItem};
9
10/// A step-by-step progress for users to navigate through a series of steps or stages.
11#[derive(IntoElement)]
12pub struct Stepper {
13    id: ElementId,
14    style: StyleRefinement,
15    items: Vec<StepperItem>,
16    step: usize,
17    layout: Axis,
18    disabled: bool,
19    size: Size,
20    text_center: bool,
21    on_click: Rc<dyn Fn(&usize, &mut Window, &mut App) + 'static>,
22}
23
24impl Stepper {
25    /// Creates a new stepper with the given ID.
26    ///
27    /// Default use is horizontal layout with step 0 selected.
28    pub fn new(id: impl Into<ElementId>) -> Self {
29        Self {
30            id: id.into(),
31            style: StyleRefinement::default(),
32            items: Vec::new(),
33            step: 0,
34            layout: Axis::Horizontal,
35            disabled: false,
36            size: Size::default(),
37            text_center: false,
38            on_click: Rc::new(|_, _, _| {}),
39        }
40    }
41
42    /// Set whether to center the text within each stepper item.
43    pub fn text_center(mut self, center: bool) -> Self {
44        self.text_center = center;
45        self
46    }
47
48    /// Set the layout of the stepper, default is horizontal.
49    pub fn layout(mut self, layout: Axis) -> Self {
50        self.layout = layout;
51        self
52    }
53
54    /// Sets the layout of the stepper to Vertical.
55    pub fn vertical(mut self) -> Self {
56        self.layout = Axis::Vertical;
57        self
58    }
59
60    /// Sets the selected index of the stepper, default is 0.
61    pub fn selected_index(mut self, index: usize) -> Self {
62        self.step = index;
63        self
64    }
65
66    /// Adds a stepper item to the stepper.
67    pub fn item(mut self, item: StepperItem) -> Self {
68        self.items.push(item);
69        self
70    }
71
72    /// Add multiple stepper items to the stepper.
73    pub fn items(mut self, items: impl IntoIterator<Item = StepperItem>) -> Self {
74        self.items.extend(items);
75        self
76    }
77
78    /// Set the disabled state of the stepper, default is false.
79    pub fn disabled(mut self, disabled: bool) -> Self {
80        self.disabled = disabled;
81        self
82    }
83
84    /// Add an on_click handler for when a step is clicked.
85    ///
86    /// The first parameter is the `step` of currently clicked item.
87    pub fn on_click<F>(mut self, f: F) -> Self
88    where
89        F: Fn(&usize, &mut Window, &mut App) + 'static,
90    {
91        self.on_click = Rc::new(f);
92        self
93    }
94}
95
96impl Sizable for Stepper {
97    fn with_size(mut self, size: impl Into<Size>) -> Self {
98        self.size = size.into();
99        self
100    }
101}
102
103impl Styled for Stepper {
104    fn style(&mut self) -> &mut StyleRefinement {
105        &mut self.style
106    }
107}
108
109impl RenderOnce for Stepper {
110    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
111        let total_items = self.items.len();
112        div()
113            .id(self.id)
114            .w_full()
115            .when(self.layout.is_horizontal(), |this| this.h_flex())
116            .when(self.layout.is_vertical(), |this| this.v_flex())
117            .refine_style(&self.style)
118            .children(self.items.into_iter().enumerate().map(|(step, item)| {
119                let is_last = step + 1 == total_items;
120                item.step(step)
121                    .with_size(self.size)
122                    .checked_step(self.step)
123                    .layout(self.layout)
124                    .text_center(self.text_center)
125                    .when(self.disabled, |this| this.disabled(true))
126                    .is_last(is_last)
127                    .on_click({
128                        let on_click = self.on_click.clone();
129                        move |_, window, cx| {
130                            on_click(&step, window, cx);
131                        }
132                    })
133            }))
134    }
135}