mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19: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="/menu" view=MenuMdPage/>
|
||||||
<Route path="/message" view=MessageMdPage/>
|
<Route path="/message" view=MessageMdPage/>
|
||||||
<Route path="/modal" view=ModalMdPage/>
|
<Route path="/modal" view=ModalMdPage/>
|
||||||
|
<Route path="/pagination" view=PaginationMdPage/>
|
||||||
<Route path="/popover" view=PopoverMdPage/>
|
<Route path="/popover" view=PopoverMdPage/>
|
||||||
<Route path="/progress" view=ProgressMdPage/>
|
<Route path="/progress" view=ProgressMdPage/>
|
||||||
<Route path="/radio" view=RadioMdPage/>
|
<Route path="/radio" view=RadioMdPage/>
|
||||||
|
|
|
@ -241,6 +241,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
value: "menu".into(),
|
value: "menu".into(),
|
||||||
label: "Menu".into(),
|
label: "Menu".into(),
|
||||||
},
|
},
|
||||||
|
MenuItemOption {
|
||||||
|
value: "pagination".into(),
|
||||||
|
label: "Pagination".into(),
|
||||||
|
},
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "tabs".into(),
|
value: "tabs".into(),
|
||||||
label: "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",
|
"MenuMdPage" => "../docs/menu/mod.md",
|
||||||
"MessageMdPage" => "../docs/message/mod.md",
|
"MessageMdPage" => "../docs/message/mod.md",
|
||||||
"ModalMdPage" => "../docs/modal/mod.md",
|
"ModalMdPage" => "../docs/modal/mod.md",
|
||||||
|
"PaginationMdPage" => "../docs/pagination/mod.md",
|
||||||
"PopoverMdPage" => "../docs/popover/mod.md",
|
"PopoverMdPage" => "../docs/popover/mod.md",
|
||||||
"ProgressMdPage" => "../docs/progress/mod.md",
|
"ProgressMdPage" => "../docs/progress/mod.md",
|
||||||
"RadioMdPage" => "../docs/radio/mod.md",
|
"RadioMdPage" => "../docs/radio/mod.md",
|
||||||
|
|
|
@ -28,6 +28,7 @@ mod menu;
|
||||||
mod message;
|
mod message;
|
||||||
pub mod mobile;
|
pub mod mobile;
|
||||||
mod modal;
|
mod modal;
|
||||||
|
mod pagination;
|
||||||
mod popover;
|
mod popover;
|
||||||
mod progress;
|
mod progress;
|
||||||
mod radio;
|
mod radio;
|
||||||
|
@ -75,6 +76,7 @@ pub use loading_bar::*;
|
||||||
pub use menu::*;
|
pub use menu::*;
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
pub use modal::*;
|
pub use modal::*;
|
||||||
|
pub use pagination::*;
|
||||||
pub use popover::*;
|
pub use popover::*;
|
||||||
pub use progress::*;
|
pub use progress::*;
|
||||||
pub use radio::*;
|
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