reinhardt_admin/pages/components/
common.rs1use std::sync::Arc;
16
17use reinhardt_pages::Signal;
18use reinhardt_pages::component::Page;
19use reinhardt_pages::page;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ButtonVariant {
24 Primary,
26 Secondary,
28 Success,
30 Danger,
32 Warning,
34}
35
36impl ButtonVariant {
37 pub fn class(&self) -> &'static str {
39 match self {
40 ButtonVariant::Primary => "admin-btn-primary",
41 ButtonVariant::Secondary => "admin-btn-secondary",
42 ButtonVariant::Success => "admin-btn-success",
43 ButtonVariant::Danger => "admin-btn-danger",
44 ButtonVariant::Warning => "admin-btn-warning",
45 }
46 }
47}
48
49pub fn button(text: &str, variant: ButtonVariant, disabled: bool, on_click: Signal<bool>) -> Page {
64 let classes = format!("admin-btn {}", variant.class());
65 let text = text.to_string();
66
67 if disabled {
68 return page!(|classes: String, text: String| {
69 button {
70 class: classes,
71 type: "button",
72 disabled: true,
73 { text }
74 }
75 })(classes, text);
76 }
77
78 page!(|classes: String, text: String, _on_click: Signal<bool>| {
79 button {
80 class: classes,
81 type: "button",
82 @click: move |_| {
83 _on_click.set(true);
84 },
85 { text }
86 }
87 })(classes, text, on_click)
88}
89
90pub fn loading_spinner() -> Page {
102 page!(|| {
103 div {
104 class: "flex justify-center items-center py-12",
105 div {
106 class: "admin-spinner",
107 role: "status",
108 span {
109 class: "sr-only",
110 "Loading..."
111 }
112 }
113 }
114 })()
115}
116
117pub fn error_display(message: &str, dismissible: bool) -> Page {
129 let message = message.to_string();
130
131 if dismissible {
132 page!(|message: String| {
133 div {
134 class: "admin-alert admin-alert-danger flex items-start justify-between animate__animated animate__shakeX",
135 role: "alert",
136 span { { message } }
137 button {
138 class: "ml-4 text-red-400 hover:text-red-600 cursor-pointer",
139 type: "button",
140 aria_label: "Close",
141 "×"
142 }
143 }
144 })(message)
145 } else {
146 page!(|message: String| {
147 div {
148 class: "admin-alert admin-alert-danger animate__animated animate__shakeX",
149 role: "alert",
150 { message }
151 }
152 })(message)
153 }
154}
155
156pub fn pagination(current_page: Signal<u64>, total_pages: u64) -> Page {
171 let current_val = current_page.get();
172 let mut nav_items = Vec::new();
173
174 let prev_disabled = current_val <= 1;
176 nav_items.push(create_page_item(
177 "Previous",
178 prev_disabled,
179 false,
180 current_page.clone(),
181 move |page: Signal<u64>| {
182 let current = page.get();
183 if current > 1 {
184 page.set(current - 1);
185 }
186 },
187 ));
188
189 let start = current_val.saturating_sub(2).max(1);
191 let end = (current_val + 2).min(total_pages);
192
193 for page_num in start..=end {
194 let is_current = page_num == current_val;
195 let page_num_str = page_num.to_string();
196 nav_items.push(create_page_item(
197 &page_num_str,
198 false,
199 is_current,
200 current_page.clone(),
201 move |page: Signal<u64>| {
202 page.set(page_num);
203 },
204 ));
205 }
206
207 let next_disabled = current_val >= total_pages;
209 nav_items.push(create_page_item(
210 "Next",
211 next_disabled,
212 false,
213 current_page,
214 move |page: Signal<u64>| {
215 let current = page.get();
216 if current < total_pages {
217 page.set(current + 1);
218 }
219 },
220 ));
221
222 page!(|nav_items: Vec<Page>| {
223 div {
224 class: "flex justify-center gap-1 mt-6",
225 { nav_items }
226 }
227 })(nav_items)
228}
229
230fn create_page_item<F>(
232 text: &str,
233 disabled: bool,
234 active: bool,
235 signal: Signal<u64>,
236 handler: F,
237) -> Page
238where
239 F: Fn(Signal<u64>) + 'static,
240{
241 let text = text.to_string();
242
243 if disabled {
244 page!(|text: String| {
245 span {
246 class: "admin-page-link admin-page-link-disabled",
247 aria_disabled: "true",
248 tabindex: (-1_i32).to_string(),
249 { text }
250 }
251 })(text)
252 } else if active {
253 page!(|text: String| {
254 span {
255 class: "admin-page-link admin-page-link-active",
256 aria_current: "page",
257 { text }
258 }
259 })(text)
260 } else {
261 let handler: Arc<dyn Fn(Signal<u64>)> = Arc::new(handler);
262 page!(|text: String, _signal: Signal<u64>, _handler: Arc<dyn Fn(Signal<u64>)>| {
263 a {
264 class: "admin-page-link",
265 href: "#",
266 @click: move |_| {
267 _handler(_signal.clone());
268 },
269 { text }
270 }
271 })(text, signal, handler)
272 }
273}
274
275pub fn search_bar(value: Signal<String>, placeholder: &str) -> Page {
293 let current_value = value.get();
294 let placeholder = placeholder.to_string();
295
296 page!(|placeholder: String, current_value: String| {
297 div {
298 class: "flex",
299 span {
300 class: "flex items-center px-3 bg-slate-100 border border-r-0 border-slate-200 rounded-l-lg text-slate-400 text-sm",
301 "🔍"
302 }
303 input {
304 class: "admin-input rounded-l-none border-l-0",
305 type: "text",
306 placeholder: placeholder,
307 value: current_value,
308 }
309 }
310 })(placeholder, current_value)
311}