dioxus_tw_components/components/molecules/sorttable/
props.rs1use crate::attributes::*;
2use crate::prelude::*;
3use dioxus::prelude::*;
4use dioxus_tw_components_macro::UiComp;
5use tailwind_fuse::tw_merge;
6
7#[derive(Clone, PartialEq)]
8pub struct SortableRow(Vec<SortableCell>);
9impl SortableRow {
10 pub fn new(cells: Vec<SortableCell>) -> Self {
11 SortableRow(cells)
12 }
13}
14impl std::ops::Deref for SortableRow {
15 type Target = Vec<SortableCell>;
16
17 fn deref(&self) -> &Self::Target {
18 &self.0
19 }
20}
21impl std::ops::DerefMut for SortableRow {
22 fn deref_mut(&mut self) -> &mut Self::Target {
23 &mut self.0
24 }
25}
26impl ToTableData for SortableRow {
27 fn headers_to_strings() -> Vec<impl ToString> {
28 vec![""]
29 }
30
31 fn to_keytype(&self) -> Vec<&KeyType> {
32 self.iter().map(|cell| &cell.sort_by).collect()
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct SortableCell {
38 content: Element,
39 style: String,
40 sort_by: KeyType,
41}
42impl SortableCell {
43 pub fn new(content: Element) -> Self {
44 SortableCell {
45 content,
46 style: String::new(),
47 sort_by: KeyType::None,
48 }
49 }
50
51 pub fn sort_by(mut self, sort_by: KeyType) -> Self {
52 self.sort_by = sort_by;
53 self
54 }
55
56 pub fn style(mut self, style: impl ToString) -> Self {
57 self.style = style.to_string();
58 self
59 }
60}
61
62pub trait Sortable: ToString + Clonable {
63 fn to_sortable(&self) -> KeyType {
64 KeyType::String(self.to_string())
65 }
66}
67
68impl Clone for Box<dyn Sortable> {
69 fn clone(&self) -> Self {
70 self.clone_box()
71 }
72}
73
74pub trait Clonable {
75 fn clone_box(&self) -> Box<dyn Sortable>;
76}
77
78impl<T: Clone + Sortable + 'static> Clonable for T {
79 fn clone_box(&self) -> Box<dyn Sortable> {
80 Box::new(self.clone())
81 }
82}
83
84pub trait ToTableData {
85 fn headers_to_strings() -> Vec<impl ToString>;
86 fn to_keytype(&self) -> Vec<&KeyType>;
87}
88
89#[derive(Clone)]
91pub enum KeyType {
92 None,
93 Element(Element),
94 String(String),
95 Integer(i128),
96 UnsignedInteger(u128),
97 Object(Box<dyn Sortable>),
98}
99
100impl PartialEq for KeyType {
101 fn eq(&self, other: &Self) -> bool {
102 match (self, other) {
103 (KeyType::None, KeyType::None) => true,
104 (KeyType::String(a), KeyType::String(b)) => a == b,
105 (KeyType::Integer(a), KeyType::Integer(b)) => a == b,
106 (KeyType::UnsignedInteger(a), KeyType::UnsignedInteger(b)) => a == b,
107 (KeyType::Object(a), KeyType::Object(b)) => a.to_sortable() == b.to_sortable(),
108 _ => false,
109 }
110 }
111}
112
113impl Eq for KeyType {}
114
115impl PartialOrd for KeyType {
116 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
117 Some(self.cmp(other))
118 }
119}
120
121impl Ord for KeyType {
122 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
123 match (self, other) {
124 (KeyType::String(a), KeyType::String(b)) => a.cmp(b),
125 (KeyType::Integer(a), KeyType::Integer(b)) => b.cmp(a),
126 (KeyType::UnsignedInteger(a), KeyType::UnsignedInteger(b)) => b.cmp(a),
127 (KeyType::Object(a), KeyType::Object(b)) => a.to_sortable().cmp(&b.to_sortable()),
128 _ => std::cmp::Ordering::Equal,
129 }
130 }
131}
132
133impl std::fmt::Display for KeyType {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 KeyType::None => {
137 write!(f, "None")
138 }
139 KeyType::String(str) => {
140 write!(f, "{str}")
141 }
142 KeyType::Integer(nb) => {
143 write!(f, "{nb}")
144 }
145 KeyType::UnsignedInteger(nb) => {
146 write!(f, "{nb}")
147 }
148 KeyType::Object(obj) => {
149 write!(f, "{}", obj.to_string())
150 }
151 _ => write!(f, ""),
152 }
153 }
154}
155
156impl std::fmt::Debug for KeyType {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 write!(
159 f,
160 "{}",
161 match self {
162 Self::None => "None",
163 Self::Element(_) => "Element",
164 Self::String(_) => "String",
165 Self::Integer(_) => "Integer",
166 Self::UnsignedInteger(_) => "UnsignedInteger",
167 _ => "Object(_)",
168 },
169 )
170 }
171}
172
173impl From<&str> for KeyType {
174 fn from(str: &str) -> Self {
175 KeyType::String(str.to_string())
176 }
177}
178
179impl From<String> for KeyType {
180 fn from(str: String) -> Self {
181 KeyType::String(str)
182 }
183}
184
185impl From<i128> for KeyType {
186 fn from(nb: i128) -> Self {
187 KeyType::Integer(nb)
188 }
189}
190
191impl From<u128> for KeyType {
192 fn from(nb: u128) -> Self {
193 KeyType::UnsignedInteger(nb)
194 }
195}
196
197impl From<i64> for KeyType {
198 fn from(nb: i64) -> Self {
199 KeyType::Integer(nb.into())
200 }
201}
202
203impl From<u64> for KeyType {
204 fn from(nb: u64) -> Self {
205 KeyType::UnsignedInteger(nb.into())
206 }
207}
208
209impl From<i32> for KeyType {
210 fn from(nb: i32) -> Self {
211 KeyType::Integer(nb.into())
212 }
213}
214
215impl From<u32> for KeyType {
216 fn from(nb: u32) -> Self {
217 KeyType::UnsignedInteger(nb.into())
218 }
219}
220
221impl From<i16> for KeyType {
222 fn from(nb: i16) -> Self {
223 KeyType::Integer(nb.into())
224 }
225}
226
227impl From<u16> for KeyType {
228 fn from(nb: u16) -> Self {
229 KeyType::UnsignedInteger(nb.into())
230 }
231}
232
233impl From<i8> for KeyType {
234 fn from(nb: i8) -> Self {
235 KeyType::Integer(nb.into())
236 }
237}
238
239impl From<u8> for KeyType {
240 fn from(nb: u8) -> Self {
241 KeyType::UnsignedInteger(nb.into())
242 }
243}
244
245#[derive(Clone, PartialEq, Props, UiComp)]
246pub struct SortTableProps {
247 #[props(extends = GlobalAttributes)]
248 attributes: Vec<Attribute>,
249
250 #[props(optional, into)]
251 header_class: Option<String>,
252
253 #[props(optional, into)]
254 row_class: Option<String>,
255
256 #[props(optional, into)]
257 cell_class: Option<String>,
258
259 #[props(optional, into)]
262 default_sort: Option<String>,
263
264 #[props(default = use_signal(|| 0), into)]
267 sorted_col_index: Signal<usize>,
268
269 headers: Vec<String>,
270
271 data: ReadOnlySignal<Vec<SortableRow>>,
272}
273
274pub struct SortTableState {
275 headers: Vec<String>,
276 data: Vec<SortableRow>,
277 sorted_col_index: Signal<usize>,
278 sort_ascending: bool,
279}
280
281impl SortTableState {
282 pub fn new(
283 headers: Vec<String>,
284 data: Vec<SortableRow>,
285 current_sort_index: Signal<usize>,
286 ) -> Self {
287 SortTableState {
288 headers,
289 data,
290 sort_ascending: true,
291 sorted_col_index: current_sort_index,
292 }
293 }
294
295 pub fn set_sorted_col_index(&mut self, sorted_col_index: usize) {
296 self.sorted_col_index.set(sorted_col_index);
297 }
298
299 pub fn get_sorted_col_index(&self) -> usize {
300 *self.sorted_col_index.read()
301 }
302
303 pub fn reverse_data(&mut self) {
304 self.data.reverse();
305 }
306
307 pub fn toggle_sort_direction(&mut self) {
308 self.sort_ascending = !self.sort_ascending;
309 }
310
311 pub fn set_sort_direction(&mut self, ascending: bool) {
312 self.sort_ascending = ascending;
313 }
314
315 pub fn is_sort_ascending(&self) -> bool {
316 self.sort_ascending
317 }
318
319 fn is_column_sortable(&self, column_index: usize) -> bool {
320 self.data
321 .first()
322 .and_then(|row| row.get(column_index))
323 .is_some_and(|cell| cell.sort_by != KeyType::None)
324 }
325
326 pub fn set_default_sort(mut self, column_name: Option<String>) -> Self {
332 let column_index = column_name
333 .and_then(|col| self.headers.iter().position(|h| h == &col))
334 .filter(|&idx| self.is_column_sortable(idx))
335 .unwrap_or(0);
336
337 self.sorted_col_index.set(column_index);
338
339 if self.is_column_sortable(column_index) {
340 sort_table_keytype(&mut self.data, |t: &SortableRow| {
341 t.to_keytype()[column_index].clone()
342 });
343 }
344
345 self
346 }
347}
348
349fn sort_table_keytype<F>(data: &mut [SortableRow], key_extractor: F)
350where
351 F: Fn(&SortableRow) -> KeyType,
352{
353 data.sort_by_key(key_extractor);
354}
355
356#[component]
357pub fn SortTable(mut props: SortTableProps) -> Element {
358 props.update_class_attribute();
359 let mut state = use_signal(|| {
360 SortTableState::new(
361 props.headers.clone(),
362 props.data.read().clone(),
363 props.sorted_col_index,
364 )
365 .set_default_sort(props.default_sort.clone())
366 });
367 use_effect(move || {
368 state.set(
369 SortTableState::new(
370 props.headers.clone(),
371 props.data.read().clone(),
372 props.sorted_col_index,
373 )
374 .set_default_sort(props.default_sort.clone()),
375 );
376 });
377
378 let header_class = match props.header_class {
379 Some(header_class) => tw_merge!("select-none cursor-pointer", header_class),
380 None => "select-none cursor-pointer".to_string(),
381 };
382
383 let row_class = match props.row_class {
384 Some(row_class) => row_class.to_string(),
385 None => String::new(),
386 };
387
388 let cell_class = match props.cell_class {
389 Some(cell_class) => cell_class.to_string(),
390 None => String::new(),
391 };
392
393 rsx! {
394 table {..props.attributes,
395 TableHeader {
396 TableRow {
397 for (index , head) in state.read().headers.iter().enumerate() {
398 TableHead {
399 class: "{header_class}",
400 onclick: move |_| {
401 if !state.peek().is_column_sortable(index) {
402 return;
403 }
404 let sorted_col_index = state.read().get_sorted_col_index();
405 if sorted_col_index == index {
406 state.write().reverse_data();
407 state.write().toggle_sort_direction();
408 } else {
409 sort_table_keytype(
410 &mut state.write().data,
411 |t: &SortableRow| t.to_keytype()[index].clone(),
412 );
413 state.write().set_sort_direction(true);
414 }
415 state.write().set_sorted_col_index(index);
416 },
417 div { class: "flex flex-row items-center justify-between space-x-1",
418 p { {head.to_string()} }
419 if state.read().is_column_sortable(index)
420 && state.read().get_sorted_col_index() == index
421 {
422 Icon {
423 class: if state.read().is_sort_ascending() { "fill-foreground transition-all -rotate-180" } else { "fill-foreground transition-all" },
424 icon: Icons::ExpandMore,
425 }
426 }
427 }
428 }
429 }
430 }
431 }
432 TableBody {
433 for data in state.read().data.iter() {
434 TableRow { class: "{row_class}",
435 for field in data.iter() {
436 TableCell { class: format!("{}", tw_merge!(& cell_class, & field.style)),
437 {field.content.clone()}
438 }
439 }
440 }
441 }
442 }
443 }
444 }
445}