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
//! An editable, reorderable list component ([`OrderedList`]) and its [`ListItem`] entry.
use leptos::prelude::*;
/// An entry in an [`OrderedList`], identified by `id` with a display `label`.
#[derive(Clone)]
pub struct ListItem {
/// The stable identifier passed to the list's callbacks.
pub id: String,
/// The text shown for this row.
pub label: String,
}
impl ListItem {
/// Creates a [`ListItem`] from an id and its display label.
pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
Self {
id: id.into(),
label: label.into(),
}
}
}
/// A reorderable list of [`ListItem`]s, showing move-up, move-down, and remove actions
/// only for the callbacks that are provided (up/down are disabled at the ends). Each
/// callback receives the affected item's id, and `on_select` fires when a row's label is
/// clicked. Renders an "Empty" placeholder when there are no items.
#[component]
pub fn OrderedList(
#[prop(into)] items: Signal<Vec<ListItem>>,
#[prop(optional)] on_move_up: Option<Callback<String>>,
#[prop(optional)] on_move_down: Option<Callback<String>>,
#[prop(optional)] on_remove: Option<Callback<String>>,
#[prop(optional)] on_select: Option<Callback<String>>,
) -> impl IntoView {
view! {
<div class="nightshade-ordered-list" role="list">
{move || {
let rows = items.get();
let count = rows.len();
if count == 0 {
return view! { <div class="nightshade-ordered-empty">"Empty"</div> }.into_any();
}
rows.into_iter()
.enumerate()
.map(|(index, item)| {
let select_id = item.id.clone();
let up_id = item.id.clone();
let down_id = item.id.clone();
let remove_id = item.id.clone();
let is_first = index == 0;
let is_last = index + 1 == count;
view! {
<div class="nightshade-ordered-row" role="listitem">
<button
class="nightshade-ordered-label"
on:click=move |_| {
if let Some(callback) = on_select {
callback.run(select_id.clone());
}
}
>
{item.label}
</button>
<div class="nightshade-ordered-actions">
{on_move_up
.map(|callback| {
view! {
<button
class="nightshade-ordered-action"
aria-label="Move up"
disabled=is_first
on:click=move |_| callback.run(up_id.clone())
>
"\u{25b2}"
</button>
}
})}
{on_move_down
.map(|callback| {
view! {
<button
class="nightshade-ordered-action"
aria-label="Move down"
disabled=is_last
on:click=move |_| callback.run(down_id.clone())
>
"\u{25bc}"
</button>
}
})}
{on_remove
.map(|callback| {
view! {
<button
class="nightshade-ordered-action danger"
aria-label="Remove"
on:click=move |_| callback.run(remove_id.clone())
>
"\u{00d7}"
</button>
}
})}
</div>
</div>
}
})
.collect_view()
.into_any()
}}
</div>
}
}