From 93cd869fd891cbc2bb5b2305a6882efab62143d6 Mon Sep 17 00:00:00 2001 From: zhiyanzhaijie Date: Wed, 4 Feb 2026 10:48:03 +0800 Subject: [PATCH 1/8] Add pagination component --- component.json | 3 +- preview/src/components/mod.rs | 1 + .../src/components/pagination/component.json | 21 ++ .../src/components/pagination/component.rs | 186 ++++++++++++++++++ preview/src/components/pagination/docs.md | 36 ++++ preview/src/components/pagination/style.css | 115 +++++++++++ .../pagination/variants/main/mod.rs | 30 +++ 7 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 preview/src/components/pagination/component.json create mode 100644 preview/src/components/pagination/component.rs create mode 100644 preview/src/components/pagination/docs.md create mode 100644 preview/src/components/pagination/style.css create mode 100644 preview/src/components/pagination/variants/main/mod.rs diff --git a/component.json b/component.json index 4e8cbadc..975a7c80 100644 --- a/component.json +++ b/component.json @@ -38,6 +38,7 @@ "preview/src/components/skeleton", "preview/src/components/card", "preview/src/components/sheet", - "preview/src/components/badge" + "preview/src/components/badge", + "preview/src/components/pagination" ] } diff --git a/preview/src/components/mod.rs b/preview/src/components/mod.rs index 747df071..f5c9fefa 100644 --- a/preview/src/components/mod.rs +++ b/preview/src/components/mod.rs @@ -76,6 +76,7 @@ examples!( label, menubar, navbar, + pagination, popover, progress, radio_group, diff --git a/preview/src/components/pagination/component.json b/preview/src/components/pagination/component.json new file mode 100644 index 00000000..6aa22f97 --- /dev/null +++ b/preview/src/components/pagination/component.json @@ -0,0 +1,21 @@ +{ + "name": "pagination", + "description": "Navigation controls for paged content.", + "authors": [ + "zhiyanzhaijie" + ], + "exclude": [ + "variants", + "docs.md", + "component.json" + ], + "cargoDependencies": [ + { + "name": "dioxus-primitives", + "git": "https://github.com/DioxusLabs/components" + } + ], + "globalAssets": [ + "../../../assets/dx-components-theme.css" + ] +} diff --git a/preview/src/components/pagination/component.rs b/preview/src/components/pagination/component.rs new file mode 100644 index 00000000..55f3ace0 --- /dev/null +++ b/preview/src/components/pagination/component.rs @@ -0,0 +1,186 @@ +use dioxus::prelude::*; + +#[derive(Copy, Clone, PartialEq, Default)] +#[non_exhaustive] +pub enum PaginationLinkSize { + #[default] + Icon, + Default, +} + +impl PaginationLinkSize { + pub fn class(&self) -> &'static str { + match self { + PaginationLinkSize::Icon => "icon", + PaginationLinkSize::Default => "default", + } + } +} + +#[derive(Copy, Clone, PartialEq)] +#[non_exhaustive] +pub enum PaginationLinkKind { + Previous, + Next, +} + +impl PaginationLinkKind { + pub fn attr(&self) -> &'static str { + match self { + PaginationLinkKind::Previous => "previous", + PaginationLinkKind::Next => "next", + } + } +} + +#[component] +pub fn Pagination( + #[props(extends = GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + document::Link { rel: "stylesheet", href: asset!("./style.css") } + nav { + class: "pagination", + "data-slot": "pagination", + role: "navigation", + aria_label: "pagination", + ..attributes, + {children} + } + } +} + +#[component] +pub fn PaginationContent( + #[props(extends = GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + ul { + class: "pagination-content", + "data-slot": "pagination-content", + ..attributes, + {children} + } + } +} + +#[component] +pub fn PaginationItem( + #[props(extends = GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + li { + class: "pagination-item", + "data-slot": "pagination-item", + ..attributes, + {children} + } + } +} + +#[derive(Props, Clone, PartialEq)] +pub struct PaginationLinkProps { + #[props(default)] + pub is_active: bool, + #[props(default)] + pub size: PaginationLinkSize, + #[props(default)] + pub data_kind: Option, + #[props(extends = GlobalAttributes)] + #[props(extends = a)] + pub attributes: Vec, + pub children: Element, +} + +#[component] +pub fn PaginationLink(props: PaginationLinkProps) -> Element { + let aria_current = if props.is_active { Some("page") } else { None }; + let data_kind = props.data_kind.map(|kind| kind.attr()); + rsx! { + a { + class: "pagination-link", + "data-slot": "pagination-link", + "data-active": props.is_active, + "data-size": props.size.class(), + "data-kind": data_kind, + aria_current: aria_current, + ..props.attributes, + {props.children} + } + } +} + +#[component] +pub fn PaginationPrevious( + #[props(extends = GlobalAttributes)] + #[props(extends = a)] + attributes: Vec, +) -> Element { + rsx! { + PaginationLink { + size: PaginationLinkSize::Default, + aria_label: "Go to previous page", + data_kind: Some(PaginationLinkKind::Previous), + attributes, + // ChevronLeft icon from lucide https://lucide.dev/icons/chevron-left + svg { + class: "pagination-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "15 6 9 12 15 18" } + } + span { class: "pagination-label", "Previous" } + } + } +} + +#[component] +pub fn PaginationNext( + #[props(extends = GlobalAttributes)] + #[props(extends = a)] + attributes: Vec, +) -> Element { + rsx! { + PaginationLink { + size: PaginationLinkSize::Default, + aria_label: "Go to next page", + data_kind: Some(PaginationLinkKind::Next), + attributes, + span { class: "pagination-label", "Next" } + // ChevronRight icon from lucide https://lucide.dev/icons/chevron-right + svg { + class: "pagination-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + polyline { points: "9 6 15 12 9 18" } + } + } + } +} + +#[component] +pub fn PaginationEllipsis( + #[props(extends = GlobalAttributes)] attributes: Vec, +) -> Element { + rsx! { + span { + class: "pagination-ellipsis", + "data-slot": "pagination-ellipsis", + aria_hidden: "true", + ..attributes, + // MoreHorizontal icon from lucide https://lucide.dev/icons/more-horizontal + svg { + class: "pagination-icon", + view_box: "0 0 24 24", + xmlns: "http://www.w3.org/2000/svg", + circle { cx: "5", cy: "12", r: "1.5" } + circle { cx: "12", cy: "12", r: "1.5" } + circle { cx: "19", cy: "12", r: "1.5" } + } + span { class: "sr-only", "More pages" } + } + } +} diff --git a/preview/src/components/pagination/docs.md b/preview/src/components/pagination/docs.md new file mode 100644 index 00000000..8ab3ff85 --- /dev/null +++ b/preview/src/components/pagination/docs.md @@ -0,0 +1,36 @@ +The pagination component provides navigational controls for paged content. It exposes a consistent structure for previous/next actions, individual page links, and an optional ellipsis for truncated ranges. + +## Component Structure + +```rust +// The Pagination component wraps the entire control. +Pagination { + // PaginationContent groups all items in a horizontal list. + PaginationContent { + // PaginationItem is the container for a single pagination element. + // Use one item at a time and swap the inner component as needed. + PaginationItem { + // PaginationPrevious renders a previous-page link. + // - Set href to your previous page url. + PaginationPrevious { href: "#" } + + // PaginationLink renders a numbered page link. + // - is_active marks the current page. + // - href sets the target page. + PaginationLink { href: "#", is_active: true, "2" } + + // PaginationEllipsis indicates truncated pages. + PaginationEllipsis {} + + // PaginationNext renders a next-page link. + // - Set href to your next page url. + PaginationNext { href: "#" } + } + } +} +``` + +## Notes + +- `PaginationLink` uses `is_active` to indicate the current page. +- `PaginationPrevious` and `PaginationNext` show labels on larger (non-mobile) screens; labels are hidden on smaller screens to keep the control compact. diff --git a/preview/src/components/pagination/style.css b/preview/src/components/pagination/style.css new file mode 100644 index 00000000..2228dfbd --- /dev/null +++ b/preview/src/components/pagination/style.css @@ -0,0 +1,115 @@ +.pagination { + display: flex; + width: 100%; + justify-content: center; + margin: 0 auto; +} + +.pagination-content { + display: flex; + align-items: center; + padding: 0; + margin: 0; + gap: 0.25rem; + list-style: none; +} + +.pagination-link { + display: inline-flex; + box-sizing: border-box; + align-items: center; + justify-content: center; + border-radius: 0.5rem; + color: var(--secondary-color-4); + font-size: 0.875rem; + font-weight: 500; + gap: 0.5rem; + line-height: 1; + text-decoration: none; + transition: background-color 0.2s ease, color 0.2s ease; +} + +.pagination-link:focus-visible { + box-shadow: 0 0 0 2px var(--focused-border-color); +} + +.pagination-link[data-size="icon"] { + width: 2.25rem; + height: 2.25rem; + padding: 0; +} + +.pagination-ellipsis { + display: flex; + align-items: center; + justify-content: center; + color: var(--secondary-color-4); +} + +.pagination-link[data-size="icon"], +.pagination-ellipsis { + width: 2.25rem; + height: 2.25rem; +} + +.pagination-link[data-size="default"] { + height: 2.25rem; + padding: 0.5rem 1rem; +} + +.pagination-link[data-active="true"] { + border: 1px solid var(--primary-color-6); + background-color: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); +} + +.pagination-link[data-active="true"]:hover { + background-color: var(--primary-color-4); +} + +.pagination-link[data-active="false"]:hover { + background-color: var(--primary-color-5); + color: var(--secondary-color-1); +} + +.pagination-link[data-kind="previous"], +.pagination-link[data-kind="next"] { + padding-right: 0.625rem; + padding-left: 0.625rem; + gap: 0.25rem; +} + +.pagination-label { + display: none; +} + +@media (width >= 640px) { + .pagination-label { + display: inline; + } +} + + +.pagination-icon { + width: 1rem; + height: 1rem; + fill: none; + stroke: currentcolor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +/* TODO: move to shared css */ + +/* Screen-reader only text */ +.sr-only { + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + padding: 0; + border: 0; + margin: -1px; + clip: rect(0, 0, 0, 0); + white-space: nowrap; +} diff --git a/preview/src/components/pagination/variants/main/mod.rs b/preview/src/components/pagination/variants/main/mod.rs new file mode 100644 index 00000000..131dce29 --- /dev/null +++ b/preview/src/components/pagination/variants/main/mod.rs @@ -0,0 +1,30 @@ +use super::super::component::*; +use dioxus::prelude::*; + +#[component] +pub fn Demo() -> Element { + rsx! { + Pagination { + PaginationContent { + PaginationItem { + PaginationPrevious { href: "#" } + } + PaginationItem { + PaginationLink { href: "#", "1" } + } + PaginationItem { + PaginationLink { href: "#", is_active: true, "2" } + } + PaginationItem { + PaginationLink { href: "#", "3" } + } + PaginationItem { + PaginationEllipsis {} + } + PaginationItem { + PaginationNext { href: "#" } + } + } + } + } +} From 8d54d81b04009e212c5971fe0c12cdc493a4e81d Mon Sep 17 00:00:00 2001 From: zhiyanzhaijie Date: Wed, 4 Feb 2026 11:00:44 +0800 Subject: [PATCH 2/8] stylelint fix --- preview/src/components/pagination/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preview/src/components/pagination/style.css b/preview/src/components/pagination/style.css index 2228dfbd..6b22926c 100644 --- a/preview/src/components/pagination/style.css +++ b/preview/src/components/pagination/style.css @@ -110,6 +110,6 @@ padding: 0; border: 0; margin: -1px; - clip: rect(0, 0, 0, 0); + clip-path: inset(50%); white-space: nowrap; } From e69ca06ebe7ed149fb5c01805b05a234e492b202 Mon Sep 17 00:00:00 2001 From: zhiyanzhaijie Date: Wed, 4 Feb 2026 11:38:00 +0800 Subject: [PATCH 3/8] fix pagination module missing --- preview/src/components/pagination/mod.rs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 preview/src/components/pagination/mod.rs diff --git a/preview/src/components/pagination/mod.rs b/preview/src/components/pagination/mod.rs new file mode 100644 index 00000000..2590c013 --- /dev/null +++ b/preview/src/components/pagination/mod.rs @@ -0,0 +1,2 @@ +mod component; +pub use component::*; From b55937b4db617f1afde2b9939968ac091663882b Mon Sep 17 00:00:00 2001 From: zhiyanzhaijie Date: Wed, 4 Feb 2026 17:04:02 +0800 Subject: [PATCH 4/8] feat button event handler for pagination --- .../src/components/pagination/component.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/preview/src/components/pagination/component.rs b/preview/src/components/pagination/component.rs index 55f3ace0..f04eff42 100644 --- a/preview/src/components/pagination/component.rs +++ b/preview/src/components/pagination/component.rs @@ -89,6 +89,9 @@ pub struct PaginationLinkProps { pub size: PaginationLinkSize, #[props(default)] pub data_kind: Option, + onclick: Option>, + onmousedown: Option>, + onmouseup: Option>, #[props(extends = GlobalAttributes)] #[props(extends = a)] pub attributes: Vec, @@ -107,6 +110,21 @@ pub fn PaginationLink(props: PaginationLinkProps) -> Element { "data-size": props.size.class(), "data-kind": data_kind, aria_current: aria_current, + onclick: move |event| { + if let Some(f) = &props.onclick { + f.call(event); + } + }, + onmousedown: move |event| { + if let Some(f) = &props.onmousedown { + f.call(event); + } + }, + onmouseup: move |event| { + if let Some(f) = &props.onmouseup { + f.call(event); + } + }, ..props.attributes, {props.children} } @@ -115,6 +133,9 @@ pub fn PaginationLink(props: PaginationLinkProps) -> Element { #[component] pub fn PaginationPrevious( + onclick: Option>, + onmousedown: Option>, + onmouseup: Option>, #[props(extends = GlobalAttributes)] #[props(extends = a)] attributes: Vec, @@ -124,6 +145,9 @@ pub fn PaginationPrevious( size: PaginationLinkSize::Default, aria_label: "Go to previous page", data_kind: Some(PaginationLinkKind::Previous), + onclick, + onmousedown, + onmouseup, attributes, // ChevronLeft icon from lucide https://lucide.dev/icons/chevron-left svg { @@ -139,6 +163,9 @@ pub fn PaginationPrevious( #[component] pub fn PaginationNext( + onclick: Option>, + onmousedown: Option>, + onmouseup: Option>, #[props(extends = GlobalAttributes)] #[props(extends = a)] attributes: Vec, @@ -148,6 +175,9 @@ pub fn PaginationNext( size: PaginationLinkSize::Default, aria_label: "Go to next page", data_kind: Some(PaginationLinkKind::Next), + onclick, + onmousedown, + onmouseup, attributes, span { class: "pagination-label", "Next" } // ChevronRight icon from lucide https://lucide.dev/icons/chevron-right From cf2c2b0d6d85bd95bd6f056ab895eb8252e9db09 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 10 Feb 2026 15:37:53 -0600 Subject: [PATCH 5/8] fix merge --- component.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component.json b/component.json index eb843ab8..fd59c4f0 100644 --- a/component.json +++ b/component.json @@ -38,7 +38,7 @@ "preview/src/components/skeleton", "preview/src/components/card", "preview/src/components/sheet", - "preview/src/components/pagination" + "preview/src/components/pagination", "preview/src/components/sidebar", "preview/src/components/badge" ] From ec3fa7f4a6428661b1736935fdcc25101cdb896f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 10 Feb 2026 15:41:51 -0600 Subject: [PATCH 6/8] a bit tighter spacing --- preview/src/components/pagination/style.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/preview/src/components/pagination/style.css b/preview/src/components/pagination/style.css index 6b22926c..f76fe16f 100644 --- a/preview/src/components/pagination/style.css +++ b/preview/src/components/pagination/style.css @@ -34,8 +34,8 @@ } .pagination-link[data-size="icon"] { - width: 2.25rem; - height: 2.25rem; + width: 2rem; + height: 2rem; padding: 0; } @@ -48,12 +48,12 @@ .pagination-link[data-size="icon"], .pagination-ellipsis { - width: 2.25rem; - height: 2.25rem; + width: 2rem; + height: 2rem; } .pagination-link[data-size="default"] { - height: 2.25rem; + height: 2rem; padding: 0.5rem 1rem; } From da64978276bdb8f7e67543e8de8355dfae063664 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 10 Feb 2026 15:46:26 -0600 Subject: [PATCH 7/8] fill ellipses --- preview/src/components/pagination/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/preview/src/components/pagination/style.css b/preview/src/components/pagination/style.css index f76fe16f..fa3d6cb0 100644 --- a/preview/src/components/pagination/style.css +++ b/preview/src/components/pagination/style.css @@ -46,6 +46,10 @@ color: var(--secondary-color-4); } +.pagination-ellipsis .pagination-icon { + fill: currentcolor; +} + .pagination-link[data-size="icon"], .pagination-ellipsis { width: 2rem; From 310579bef424a73e2506c846e19557e46e357240 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 10 Feb 2026 15:51:58 -0600 Subject: [PATCH 8/8] slightly rounder --- preview/src/components/pagination/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preview/src/components/pagination/style.css b/preview/src/components/pagination/style.css index fa3d6cb0..ae0cf434 100644 --- a/preview/src/components/pagination/style.css +++ b/preview/src/components/pagination/style.css @@ -19,7 +19,7 @@ box-sizing: border-box; align-items: center; justify-content: center; - border-radius: 0.5rem; + border-radius: 0.625rem; color: var(--secondary-color-4); font-size: 0.875rem; font-weight: 500;