Pagination*

Rust/UI component that displays a pagination component.

navigation
  • Rust/UI Icons - CopyCopy Demo
use leptos::prelude::*;

use crate::components::ui::pagination::{
    Pagination, PaginationActive, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink,
    PaginationNext, PaginationPrevious,
};

#[component]
pub fn DemoPagination() -> impl IntoView {
    view! {
        <Pagination attr:role="navigation" attr:aria-label="pagination">
            <PaginationContent>
                <PaginationItem>
                    <PaginationPrevious href="#" />
                </PaginationItem>
                <PaginationItem>
                    <PaginationLink attr:href="#">1</PaginationLink>
                </PaginationItem>
                <PaginationItem>
                    <PaginationActive attr:aria-current="page" attr:data-active="true" attr:href="#">
                        2
                    </PaginationActive>
                </PaginationItem>
                <PaginationItem>
                    <PaginationLink attr:href="#">3</PaginationLink>
                </PaginationItem>
                <PaginationItem>
                    <PaginationEllipsis />
                </PaginationItem>
                <PaginationItem>
                    <PaginationNext href="#" />
                </PaginationItem>
            </PaginationContent>
        </Pagination>
    }
}

Installation

You can run either of the following commands:

# cargo install ui-cli --force
ui add demo_pagination
ui add pagination

Update the imports to match your project setup.

Copy and paste the following code into your project:

components/ui/pagination.rs

use icons::{ChevronLeft, ChevronRight, Ellipsis};
use leptos::prelude::*;
use leptos_ui::clx;
use leptos_ui::clx::IntoTailwindClass;

use crate::registry::ui::button::{ButtonClass, ButtonSize, ButtonVariant};

mod components {
    use super::*;
    clx! {Pagination, nav, "flex justify-center mx-auto w-full"}
    clx! {PaginationContent, ul, "flex flex-row gap-1 items-center"}
    clx! {PaginationItem, li, ""}
    clx! {EllipsisRoot, span, "flex justify-center items-center size-9 [&_svg:not([class*='size-'])]:size-4"}

    const COMMON_CLASSES: &str = "inline-flex justify-center items-center text-sm font-medium whitespace-nowrap rounded-md outline-none  hover:bg-accent hover:text-accent-foreground transition-all  disabled:opacity-50 disabled:pointer-events-none [&_svg]:pointer-events-none  [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0  aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40  focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]";

    // TODO. Merge common classes first.

    clx! {PaginationLink, a, COMMON_CLASSES, "gap-2  size-9 dark:hover:bg-accent/50"}

    clx! {PaginationActive, a, COMMON_CLASSES, "gap-2 border  bg-background shadow-xs size-9 dark:bg-input/30 dark:border-input dark:hover:bg-input/50"}

    clx! {RootPrevious, a, COMMON_CLASSES,  "py-2 has-[>svg]:px-3 dark:hover:bg-accent/50"}

    clx! {RootNext, a, COMMON_CLASSES, "h-9  has-[>svg]:px-3 dark:hover:bg-accent/50"}
}

pub use components::*;

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[component]
pub fn PaginationNext(href: &'static str) -> impl IntoView {
    view! {
        <RootNext class="gap-1 py-2 px-2.5 sm:pr-2.5" attr:aria-label="Go to next page" attr:href=href>
            <span class="hidden sm:block">Next</span>
            <ChevronRight />
        </RootNext>
    }
}

#[component]
pub fn PaginationPrevious(href: &'static str) -> impl IntoView {
    view! {
        <RootPrevious class="gap-1 px-2.5 h-9 sm:pl-2.5" attr:aria-label="Go to previous page" attr:href=href>
            <ChevronLeft />
            <span class="hidden sm:block">Previous</span>
        </RootPrevious>
    }
}

#[component]
pub fn PaginationEllipsis() -> impl IntoView {
    view! {
        <EllipsisRoot attr:aria-hidden="true">
            <Ellipsis />
            <span class="sr-only">More pages</span>
        </EllipsisRoot>
    }
}

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

// TODO. Merge properly with Button
#[component]
pub fn PaginationLinkWithButton(
    #[prop(into, optional)] variant: Signal<ButtonVariant>,
    #[prop(into, optional)] size: Signal<ButtonSize>,
    #[prop(into, optional)] class: String,
    #[prop(into, optional)] _is_active: Option<bool>,
    #[prop(into)] href: &'static str,
    children: Children,
) -> impl IntoView {
    // TODO. should be ButtonVariant::Ghost OR ButtonVariant::Outline if is_active
    let merged_class = Memo::new(move |_| {
        let variant = variant.get();
        let size = size.get();
        let button = ButtonClass { variant, size };
        button.with_class(class.clone())
    });

    view! {
        <a class=merged_class href=href>
            {children()}
        </a>
    }
}

Update the imports to match your project setup.

Usage

// Coming soon 🦀