Skip to main content

dioxus_tw_components/components/
pagination.rs

1use crate::components::{button::*, icon::*};
2use dioxus::dioxus_core::AttributeValue;
3use dioxus::prelude::*;
4
5#[derive(Clone, PartialEq, Props)]
6pub struct PaginationProps {
7    #[props(extends = div, extends = GlobalAttributes)]
8    attributes: Vec<Attribute>,
9
10    #[props(optional, default)]
11    pub class: ReadSignal<String>,
12    #[props(into)]
13    pub data_size: ReadSignal<usize>,
14    #[props(into)]
15    pub page_size: ReadSignal<usize>,
16    pub page_number: Signal<usize>,
17}
18
19#[component]
20pub fn Pagination(mut props: PaginationProps) -> Element {
21    let default_classes = "pagination";
22    crate::setup_class_attribute(&mut props.attributes, default_classes);
23
24    let max_pages = use_memo(move || (*props.data_size.read() / *props.page_size.read()) + 1);
25
26    let data_variant_attribute = match props
27        .attributes
28        .iter()
29        .find(|attr| attr.name == "data-variant")
30    {
31        Some(attribute) => {
32            if let AttributeValue::Text(ref value) = attribute.value {
33                value.clone()
34            } else {
35                "none".to_string()
36            }
37        }
38        _ => "none".to_string(),
39    };
40    let data_color_attribute = match props
41        .attributes
42        .iter()
43        .find(|attr| attr.name == "data-style")
44    {
45        Some(attribute) => {
46            if let AttributeValue::Text(ref value) = attribute.value {
47                value.clone()
48            } else {
49                "none".to_string()
50            }
51        }
52        _ => "none".to_string(),
53    };
54
55    let prev_dots = use_memo(move || *props.page_number.read() > 2);
56    let next_dots =
57        use_memo(move || *props.page_number.read() <= max_pages.read().checked_sub(2).unwrap_or(0));
58
59    rsx! {
60        div { class: format!("{} {}", default_classes, props.class),
61            ..props.attributes,
62            Button {
63                class: format!("pagination-nav-button {}", props.class),
64                "data-variant": data_variant_attribute.clone(),
65                "data-style": data_color_attribute.clone(),
66                disabled: *props.page_number.read() == 1,
67                onclick: move |_event: MouseEvent| {
68                    let value = *props.page_number.peek();
69                    props.page_number.set(value - 1);
70                },
71                Icon { style: "font-family: 'Material Symbols Rounded'; user-select: none; font-size: 1.5rem;", icon: Icons::ArrowLeft }
72            }
73            if *prev_dots.read() {
74                Button {
75                    class: props.class,
76                    disabled: *props.page_number.read() == 1,
77                    "data-variant": data_variant_attribute.clone(),
78                    "data-style": data_color_attribute.clone(),
79                    onclick: move |_event: MouseEvent| {
80                        props.page_number.set(1);
81                    },
82                    "1"
83                }
84                p { class: "pagination-dots", "..." }
85            }
86            for page in (std::cmp::max(1_isize, *props.page_number.read() as isize - 1)
87                as usize)..=std::cmp::min(*max_pages.read(), *props.page_number.read() + 1)
88            {
89                Button {
90                    class: props.class,
91                    "data-variant": data_variant_attribute.clone(),
92                    "data-style": data_color_attribute.clone(),
93                    disabled: *props.page_number.read() == page,
94                    onclick: move |_event: MouseEvent| {
95                        props.page_number.set(page);
96                    },
97                    "{page}"
98                }
99            }
100            if *next_dots.read() {
101                p { class: "pagination-dots", "..." }
102                Button {
103                    class: props.class,
104                    "data-variant": data_variant_attribute.clone(),
105                    "data-style": data_color_attribute.clone(),
106                    disabled: *props.page_number.read() == *max_pages.read(),
107                    onclick: move |_event: MouseEvent| {
108                        props.page_number.set(*max_pages.peek());
109                    },
110                    "{*max_pages.read()}"
111                }
112            }
113            Button {
114                class: format!("pagination-nav-button {}", props.class),
115                "data-variant": data_variant_attribute.clone(),
116                "data-style": data_color_attribute.clone(),
117                disabled: *props.page_number.read() == *max_pages.read(),
118                onclick: move |_event: MouseEvent| {
119                    let value = *props.page_number.peek();
120                    props.page_number.set(value + 1);
121                },
122                Icon { style: "font-family: 'Material Symbols Rounded'; user-select: none; font-size: 1.5rem;", icon: Icons::ArrowRight }
123            }
124        }
125    }
126}