mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: Pagination (#212)
* feat: Pagination * feat: optimize Pagination component * fix: build nightly --------- Co-authored-by: luoxiao <luoxiaozero@163.com>
This commit is contained in:
parent
68f81a04eb
commit
96f74a122e
8 changed files with 253 additions and 0 deletions
|
@ -73,6 +73,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
|||
<Route path="/menu" view=MenuMdPage/>
|
||||
<Route path="/message" view=MessageMdPage/>
|
||||
<Route path="/modal" view=ModalMdPage/>
|
||||
<Route path="/pagination" view=PaginationMdPage/>
|
||||
<Route path="/popover" view=PopoverMdPage/>
|
||||
<Route path="/progress" view=ProgressMdPage/>
|
||||
<Route path="/radio" view=RadioMdPage/>
|
||||
|
|
|
@ -241,6 +241,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
|||
value: "menu".into(),
|
||||
label: "Menu".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "pagination".into(),
|
||||
label: "Pagination".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "tabs".into(),
|
||||
label: "Tabs".into(),
|
||||
|
|
49
demo_markdown/docs/pagination/mod.md
Normal file
49
demo_markdown/docs/pagination/mod.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Pagination
|
||||
|
||||
```rust demo
|
||||
let page = create_rw_signal(1);
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<div>"Page: " {move || page.get()}</div>
|
||||
<Pagination page count=10 />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Pagination count=100 size=ButtonSize::Tiny />
|
||||
<Pagination count=100 size=ButtonSize::Small />
|
||||
<Pagination count=100 size=ButtonSize::Medium />
|
||||
<Pagination count=100 size=ButtonSize::Large />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination ranges
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Pagination count=100 sibling_count=0 />
|
||||
<Pagination count=100 sibling_count=1 />
|
||||
<Pagination count=100 sibling_count=2 />
|
||||
<Pagination count=100 sibling_count=3 />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Additional classes. |
|
||||
| page | `Model<usize>` | `1` | The current page starts from 1. |
|
||||
| count | `MaybeSignal<usize>` | | The total numbers of pages. |
|
||||
| sibling_count | `MaybeSignal<usize>` | `1` | Number of visible pages after and before the current page. |
|
||||
| size | `MaybeSignal<ButtonSize>` | `ButtonSize::Medium` | Button size. |
|
||||
| on_change | `Option<Callback<usize>>` | `None` | Callback fired when the page is changed. |
|
|
@ -54,6 +54,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
"MenuMdPage" => "../docs/menu/mod.md",
|
||||
"MessageMdPage" => "../docs/message/mod.md",
|
||||
"ModalMdPage" => "../docs/modal/mod.md",
|
||||
"PaginationMdPage" => "../docs/pagination/mod.md",
|
||||
"PopoverMdPage" => "../docs/popover/mod.md",
|
||||
"ProgressMdPage" => "../docs/progress/mod.md",
|
||||
"RadioMdPage" => "../docs/radio/mod.md",
|
||||
|
|
|
@ -28,6 +28,7 @@ mod menu;
|
|||
mod message;
|
||||
pub mod mobile;
|
||||
mod modal;
|
||||
mod pagination;
|
||||
mod popover;
|
||||
mod progress;
|
||||
mod radio;
|
||||
|
@ -75,6 +76,7 @@ pub use loading_bar::*;
|
|||
pub use menu::*;
|
||||
pub use message::*;
|
||||
pub use modal::*;
|
||||
pub use pagination::*;
|
||||
pub use popover::*;
|
||||
pub use progress::*;
|
||||
pub use radio::*;
|
||||
|
|
179
thaw/src/pagination/mod.rs
Normal file
179
thaw/src/pagination/mod.rs
Normal file
|
@ -0,0 +1,179 @@
|
|||
use crate::{Button, ButtonSize, ButtonVariant};
|
||||
use leptos::*;
|
||||
use std::cmp::min;
|
||||
use thaw_utils::{class_list, mount_style, Model, OptionalProp, StoredMaybeSignal};
|
||||
|
||||
fn range(start: usize, end: usize) -> Vec<PaginationItem> {
|
||||
let mut ret = vec![];
|
||||
for idx in start..=end {
|
||||
ret.push(PaginationItem::Number(idx));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
enum PaginationItem {
|
||||
DotLeft,
|
||||
DotRight,
|
||||
Number(usize),
|
||||
}
|
||||
|
||||
fn use_pagination(page: usize, count: usize, sibling_count: usize) -> Vec<PaginationItem> {
|
||||
// Pages count is determined as siblingCount + firstPage + lastPage + currentPage + 2*DOTS
|
||||
let total_page_numbers = sibling_count + 5;
|
||||
// Case 1:
|
||||
// If the number of pages is less than the page numbers we want to show in our
|
||||
// paginationComponent, we return the range [1..totalPageCount]
|
||||
if total_page_numbers >= count {
|
||||
return range(1, count);
|
||||
}
|
||||
let current_page = page;
|
||||
// Calculate left and right sibling index and make sure they are within range 1 and totalPageCount
|
||||
let left_sibling_index = if current_page > sibling_count + 1 {
|
||||
current_page - sibling_count
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let right_sibling_index = min(current_page + sibling_count, count);
|
||||
// We do not show dots just when there is just one page number to be inserted between the extremes of sibling and the page limits i.e 1 and totalPageCount.
|
||||
// Hence we are using leftSiblingIndex > 2 and rightSiblingIndex < totalPageCount - 2
|
||||
let should_show_left_dots = left_sibling_index > 2;
|
||||
let should_show_right_dots = right_sibling_index < count - 2;
|
||||
|
||||
let first_page_index = 1;
|
||||
let last_page_index = count;
|
||||
|
||||
// Case 2: No left dots to show, but rights dots to be shown
|
||||
if !should_show_left_dots && should_show_right_dots {
|
||||
let left_item_count = 3 + 2 * sibling_count;
|
||||
let mut left_range = range(1, left_item_count);
|
||||
left_range.push(PaginationItem::DotRight);
|
||||
left_range.push(PaginationItem::Number(count));
|
||||
left_range
|
||||
} else if should_show_left_dots && !should_show_right_dots {
|
||||
// Case 3: No right dots to show, but left dots to be shown
|
||||
let right_item_count = 3 + 2 * sibling_count;
|
||||
let mut right_range = range(count - right_item_count + 1, count);
|
||||
let mut ret = vec![
|
||||
PaginationItem::Number(first_page_index),
|
||||
PaginationItem::DotLeft,
|
||||
];
|
||||
ret.append(&mut right_range);
|
||||
ret
|
||||
} else {
|
||||
// Case 4: Both left and right dots to be shown
|
||||
let mut middle_range = range(left_sibling_index, right_sibling_index);
|
||||
let mut range = vec![
|
||||
PaginationItem::Number(first_page_index),
|
||||
PaginationItem::DotLeft,
|
||||
];
|
||||
range.append(&mut middle_range);
|
||||
range.append(&mut vec![
|
||||
PaginationItem::DotRight,
|
||||
PaginationItem::Number(last_page_index),
|
||||
]);
|
||||
range
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Pagination(
|
||||
#[prop(default = 1.into(), into)] page: Model<usize>,
|
||||
#[prop(into)] count: MaybeSignal<usize>,
|
||||
#[prop(default = 1.into(), into)] sibling_count: MaybeSignal<usize>,
|
||||
#[prop(optional, into)] on_change: Option<Callback<usize>>,
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional, into)] size: MaybeSignal<ButtonSize>,
|
||||
) -> impl IntoView {
|
||||
mount_style("pagination", include_str!("./pagination.css"));
|
||||
|
||||
let size: StoredMaybeSignal<_> = size.into();
|
||||
let no_next = Memo::new(move |_| page.get() == count.get());
|
||||
let no_previous = Memo::new(move |_| page.get() == 1);
|
||||
|
||||
let on_click_previous = Callback::<ev::MouseEvent>::new(move |_| {
|
||||
page.update(|val| *val -= 1);
|
||||
if let Some(callback) = on_change.as_ref() {
|
||||
callback.call(page.get())
|
||||
}
|
||||
});
|
||||
|
||||
let on_click_next = Callback::<ev::MouseEvent>::new(move |_| {
|
||||
page.update(|val| *val += 1);
|
||||
if let Some(callback) = on_change.as_ref() {
|
||||
callback.call(page.get())
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<nav class=class_list!["thaw-pagination", class.map(| c | move || c.get())]>
|
||||
<ul>
|
||||
<li>
|
||||
<Button
|
||||
size=size
|
||||
on_click=on_click_previous
|
||||
variant=ButtonVariant::Text
|
||||
icon=icondata_ai::AiLeftOutlined
|
||||
disabled=no_previous
|
||||
circle=true
|
||||
/>
|
||||
</li>
|
||||
|
||||
<For
|
||||
each=move || use_pagination(page.get(), count.get(), sibling_count.get())
|
||||
key=|item| match item {
|
||||
PaginationItem::DotLeft => -2,
|
||||
PaginationItem::DotRight => -1,
|
||||
PaginationItem::Number(nb) => nb.clone() as i64
|
||||
}
|
||||
let:item
|
||||
>
|
||||
{
|
||||
if let PaginationItem::Number(nb) = item {
|
||||
view! {
|
||||
<li>
|
||||
<Button
|
||||
size=size
|
||||
style=Memo::new(move |_| if page.get() == nb {
|
||||
"color: var(--thaw-font-color-hover); border-color: var(--thaw-border-color-hover);".to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
})
|
||||
variant=Memo::new(move |_| if page.get() == nb {
|
||||
ButtonVariant::Outlined
|
||||
} else {
|
||||
ButtonVariant::Text
|
||||
})
|
||||
on_click=Callback::new(move |_: ev::MouseEvent| {
|
||||
if page.get() != nb {
|
||||
page.set(nb)
|
||||
}
|
||||
if let Some(callback) = on_change.as_ref() {
|
||||
callback.call(page.get())
|
||||
}
|
||||
})
|
||||
round=true
|
||||
>
|
||||
{nb}
|
||||
</Button>
|
||||
</li>
|
||||
}
|
||||
} else {
|
||||
view! {
|
||||
<li>"..."</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
</For>
|
||||
<li>
|
||||
<Button
|
||||
size
|
||||
on_click=on_click_next
|
||||
variant=ButtonVariant::Text
|
||||
icon=icondata_ai::AiRightOutlined disabled=no_next
|
||||
circle=true
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
}
|
8
thaw/src/pagination/pagination.css
Normal file
8
thaw/src/pagination/pagination.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.thaw-pagination > ul {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
}
|
|
@ -94,3 +94,12 @@ impl<T> From<MaybeSignal<T>> for StoredMaybeSignal<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> From<StoredMaybeSignal<T>> for MaybeSignal<T> {
|
||||
fn from(value: StoredMaybeSignal<T>) -> Self {
|
||||
match value {
|
||||
StoredMaybeSignal::StoredValue(value) => MaybeSignal::Static(value.get_value()),
|
||||
StoredMaybeSignal::Signal(singal) => MaybeSignal::Dynamic(singal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue