1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! 步骤条。
//!
//! 向导式流程指示:序号圆点 + 标题 + 连接线,当前步骤高亮,已完成打勾。
use crate::{prelude::*, *};
/// 单个步骤。
#[derive(Clone)]
pub struct StepItem {
/// 标题。
pub title: SharedString,
/// 描述(可选)。
pub description: Option<SharedString>,
}
impl StepItem {
/// 创建步骤。
pub fn new(title: impl Into<SharedString>) -> Self {
Self {
title: title.into(),
description: None,
}
}
/// 设置描述。
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
self.description = Some(description.into());
self
}
}
/// 步骤条。
#[derive(IntoElement)]
pub struct Steps {
/// 步骤列表。
items: Vec<StepItem>,
/// 当前步骤下标(0 起始)。
current: usize,
/// 用户样式。
style: StyleRefinement,
}
impl Steps {
/// 创建步骤条。
pub fn new(items: Vec<StepItem>, current: usize) -> Self {
Self {
items,
current,
style: StyleRefinement::default(),
}
}
}
impl Styled for Steps {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for Steps {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let accent = theme.tokens.accent.color;
let muted_foreground = theme.tokens.muted_foreground.color;
let border = theme.tokens.border.color;
let user_style = self.style;
let current = self.current;
let total = self.items.len();
div()
.flex()
.flex_row()
.items_start()
.w_full()
.children(self.items.into_iter().enumerate().map(|(ix, item)| {
let done = ix < current;
let active = ix == current;
let dot = if done { "✓" } else { &ix.to_string() };
div()
.flex()
.flex_row()
.flex_1()
.items_start()
.gap(px(8.0))
.child(
div()
.w(px(24.0))
.h(px(24.0))
.flex()
.items_center()
.justify_center()
.rounded_full()
.text_xs()
.border_1()
.border_color(if done || active { accent } else { border })
.when(done || active, |this| this.bg(accent.opacity(0.15)))
.text_color(if done || active {
accent
} else {
muted_foreground
})
.child(dot.to_string()),
)
.child(
div()
.flex()
.flex_col()
.flex_1()
.gap(px(2.0))
.child(
div()
.text_sm()
.text_color(if active { accent } else { muted_foreground })
.child(item.title.clone()),
)
.when_some(item.description, |this, desc| {
this.child(div().text_xs().text_color(muted_foreground).child(desc))
}),
)
.when(ix + 1 < total, |this| {
this.child(div().flex_1().h(px(1.0)).mt(px(12.0)).bg(if done {
accent
} else {
border
}))
})
.into_any_element()
}))
.map(|mut this| {
this.style().refine(&user_style);
this
})
}
}