nightshade_api/web/
asset_grid.rs1use leptos::prelude::*;
4
5#[derive(Clone)]
8pub struct AssetItem {
9 pub id: String,
11 pub label: String,
13 pub thumbnail: String,
15 pub subtitle: String,
17}
18
19impl AssetItem {
20 pub fn new(
23 id: impl Into<String>,
24 label: impl Into<String>,
25 thumbnail: impl Into<String>,
26 ) -> Self {
27 Self {
28 id: id.into(),
29 label: label.into(),
30 thumbnail: thumbnail.into(),
31 subtitle: String::new(),
32 }
33 }
34
35 pub fn with_subtitle(mut self, subtitle: impl Into<String>) -> Self {
37 self.subtitle = subtitle.into();
38 self
39 }
40}
41
42#[component]
46pub fn AssetGrid(
47 #[prop(into)] items: Signal<Vec<AssetItem>>,
48 on_select: Callback<String>,
49 #[prop(default = true)] searchable: bool,
50 #[prop(into, optional)] placeholder: String,
51) -> impl IntoView {
52 let query = RwSignal::new(String::new());
53 let placeholder = if placeholder.is_empty() {
54 "Search assets…".to_string()
55 } else {
56 placeholder
57 };
58
59 let filtered = move || {
60 let needle = query.get().to_lowercase();
61 items
62 .get()
63 .into_iter()
64 .filter(|item| needle.is_empty() || item.label.to_lowercase().contains(&needle))
65 .collect::<Vec<_>>()
66 };
67
68 view! {
69 <div class="nightshade-asset-grid">
70 {searchable
71 .then(|| {
72 view! {
73 <input
74 class="nightshade-asset-search"
75 type="search"
76 placeholder=placeholder.clone()
77 prop:value=move || query.get()
78 on:input=move |event| query.set(event_target_value(&event))
79 />
80 }
81 })}
82 <div class="nightshade-asset-cards">
83 {move || {
84 let rows = filtered();
85 if rows.is_empty() {
86 return view! { <div class="nightshade-asset-empty">"No assets"</div> }
87 .into_any();
88 }
89 rows.into_iter()
90 .map(|item| {
91 let id = item.id.clone();
92 let label = item.label.clone();
93 let subtitle = item.subtitle.clone();
94 view! {
95 <button
96 class="nightshade-asset-card"
97 title=label.clone()
98 on:click=move |_| on_select.run(id.clone())
99 >
100 <span class="nightshade-asset-thumb">
101 <img loading="lazy" src=item.thumbnail alt=label.clone() />
102 </span>
103 <span class="nightshade-asset-label">{label.clone()}</span>
104 {(!subtitle.is_empty())
105 .then(|| {
106 view! {
107 <span class="nightshade-asset-subtitle">{subtitle}</span>
108 }
109 })}
110 </button>
111 }
112 })
113 .collect_view()
114 .into_any()
115 }}
116 </div>
117 </div>
118 }
119}