diff --git a/demo/src/app.rs b/demo/src/app.rs index 191a826..5c8cfff 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -73,6 +73,7 @@ fn TheRouter(is_routing: RwSignal) -> impl IntoView { + diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index 4864780..f4d1cd2 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -241,6 +241,10 @@ pub(crate) fn gen_menu_data() -> Vec { value: "menu".into(), label: "Menu".into(), }, + MenuItemOption { + value: "pagination".into(), + label: "Pagination".into(), + }, MenuItemOption { value: "tabs".into(), label: "Tabs".into(), diff --git a/demo_markdown/docs/pagination/mod.md b/demo_markdown/docs/pagination/mod.md new file mode 100644 index 0000000..6da1f59 --- /dev/null +++ b/demo_markdown/docs/pagination/mod.md @@ -0,0 +1,49 @@ +# Pagination + +```rust demo +let page = create_rw_signal(1); + +view! { + +
"Page: " {move || page.get()}
+ +
+} +``` + +### Size + +```rust demo +view! { + + + + + + +} +``` + +### Pagination ranges + +```rust demo +view! { + + + + + + +} +``` + +### Pagination Props + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| class | `OptionalProp>` | `Default::default()` | Additional classes. | +| page | `Model` | `1` | The current page starts from 1. | +| count | `MaybeSignal` | | The total numbers of pages. | +| sibling_count | `MaybeSignal` | `1` | Number of visible pages after and before the current page. | +| size | `MaybeSignal` | `ButtonSize::Medium` | Button size. | +| on_change | `Option>` | `None` | Callback fired when the page is changed. | diff --git a/demo_markdown/src/lib.rs b/demo_markdown/src/lib.rs index 680e4e2..d810344 100644 --- a/demo_markdown/src/lib.rs +++ b/demo_markdown/src/lib.rs @@ -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", diff --git a/thaw/src/lib.rs b/thaw/src/lib.rs index 802e7ba..f7e4ad4 100644 --- a/thaw/src/lib.rs +++ b/thaw/src/lib.rs @@ -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::*; diff --git a/thaw/src/pagination/mod.rs b/thaw/src/pagination/mod.rs new file mode 100644 index 0000000..7b45fe2 --- /dev/null +++ b/thaw/src/pagination/mod.rs @@ -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 { + 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 { + // 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, + #[prop(into)] count: MaybeSignal, + #[prop(default = 1.into(), into)] sibling_count: MaybeSignal, + #[prop(optional, into)] on_change: Option>, + #[prop(optional, into)] class: OptionalProp>, + #[prop(optional, into)] size: MaybeSignal, +) -> 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::::new(move |_| { + page.update(|val| *val -= 1); + if let Some(callback) = on_change.as_ref() { + callback.call(page.get()) + } + }); + + let on_click_next = Callback::::new(move |_| { + page.update(|val| *val += 1); + if let Some(callback) = on_change.as_ref() { + callback.call(page.get()) + } + }); + + view! { + + } +} diff --git a/thaw/src/pagination/pagination.css b/thaw/src/pagination/pagination.css new file mode 100644 index 0000000..46a1d71 --- /dev/null +++ b/thaw/src/pagination/pagination.css @@ -0,0 +1,8 @@ +.thaw-pagination > ul { + list-style-type: none; + display: flex; + padding: 0; + margin: 0; + column-gap: 5px; + align-items: center; +} diff --git a/thaw_utils/src/signals/stored_maybe_signal.rs b/thaw_utils/src/signals/stored_maybe_signal.rs index fedbe40..8ef0f19 100644 --- a/thaw_utils/src/signals/stored_maybe_signal.rs +++ b/thaw_utils/src/signals/stored_maybe_signal.rs @@ -94,3 +94,12 @@ impl From> for StoredMaybeSignal { } } } + +impl From> for MaybeSignal { + fn from(value: StoredMaybeSignal) -> Self { + match value { + StoredMaybeSignal::StoredValue(value) => MaybeSignal::Static(value.get_value()), + StoredMaybeSignal::Signal(singal) => MaybeSignal::Dynamic(singal), + } + } +}