Skip to main content

dioxus_bootstrap_css/
pagination.rs

1use dioxus::prelude::*;
2
3use crate::types::Size;
4
5/// Bootstrap Pagination component.
6///
7/// ```rust
8/// let page = use_signal(|| 1usize);
9/// rsx! {
10///     Pagination { current: page, total: 10 }
11/// }
12/// ```
13#[derive(Clone, PartialEq, Props)]
14pub struct PaginationProps {
15    /// Signal controlling the current page (1-based).
16    pub current: Signal<usize>,
17    /// Total number of pages.
18    pub total: usize,
19    /// Number of page links to show around the current page.
20    #[props(default = 2)]
21    pub window: usize,
22    /// Pagination size.
23    #[props(default)]
24    pub size: Size,
25    /// Show previous/next buttons.
26    #[props(default = true)]
27    pub show_prev_next: bool,
28    /// Additional CSS classes.
29    #[props(default)]
30    pub class: String,
31}
32
33#[component]
34pub fn Pagination(props: PaginationProps) -> Element {
35    let current = *props.current.read();
36    let mut page_signal = props.current;
37    let total = props.total;
38
39    if total == 0 {
40        return rsx! {};
41    }
42
43    let size_class = match props.size {
44        Size::Md => String::new(),
45        s => format!(" pagination-{s}"),
46    };
47
48    let full_class = if props.class.is_empty() {
49        format!("pagination{size_class}")
50    } else {
51        format!("pagination{size_class} {}", props.class)
52    };
53
54    // Calculate visible page range
55    let start = if current > props.window {
56        current - props.window
57    } else {
58        1
59    };
60    let end = if current + props.window <= total {
61        current + props.window
62    } else {
63        total
64    };
65
66    rsx! {
67        nav { "aria-label": "Page navigation",
68            ul { class: "{full_class}",
69                // Previous
70                if props.show_prev_next {
71                    li { class: if current <= 1 { "page-item disabled" } else { "page-item" },
72                        button {
73                            class: "page-link",
74                            disabled: current <= 1,
75                            onclick: move |_| {
76                                if current > 1 {
77                                    page_signal.set(current - 1);
78                                }
79                            },
80                            "aria-label": "Previous",
81                            span { "aria-hidden": "true", "\u{2039}" }
82                        }
83                    }
84                }
85
86                // First page + ellipsis
87                if start > 1 {
88                    li { class: "page-item",
89                        button {
90                            class: "page-link",
91                            onclick: move |_| page_signal.set(1),
92                            "1"
93                        }
94                    }
95                    if start > 2 {
96                        li { class: "page-item disabled",
97                            span { class: "page-link", "\u{2026}" }
98                        }
99                    }
100                }
101
102                // Page numbers
103                for p in start..=end {
104                    li {
105                        class: if p == current { "page-item active" } else { "page-item" },
106                        button {
107                            class: "page-link",
108                            "aria-current": if p == current { "page" } else { "" },
109                            onclick: move |_| page_signal.set(p),
110                            "{p}"
111                        }
112                    }
113                }
114
115                // Last page + ellipsis
116                if end < total {
117                    if end < total - 1 {
118                        li { class: "page-item disabled",
119                            span { class: "page-link", "\u{2026}" }
120                        }
121                    }
122                    li { class: "page-item",
123                        button {
124                            class: "page-link",
125                            onclick: move |_| page_signal.set(total),
126                            "{total}"
127                        }
128                    }
129                }
130
131                // Next
132                if props.show_prev_next {
133                    li { class: if current >= total { "page-item disabled" } else { "page-item" },
134                        button {
135                            class: "page-link",
136                            disabled: current >= total,
137                            onclick: move |_| {
138                                if current < total {
139                                    page_signal.set(current + 1);
140                                }
141                            },
142                            "aria-label": "Next",
143                            span { "aria-hidden": "true", "\u{203A}" }
144                        }
145                    }
146                }
147            }
148        }
149    }
150}