product list

This commit is contained in:
hamid zarghami
2025-09-23 09:07:32 +03:30
parent 2a4608e258
commit f8ff05357d
10 changed files with 547 additions and 9 deletions
+89
View File
@@ -0,0 +1,89 @@
import { type FC } from 'react';
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationEllipsis,
} from './ui/pagination';
import { type PagerType } from '../pages/products/types/Types';
interface PaginationUiProps {
pager: PagerType | null | undefined;
onPageChange: (page: number) => void;
}
const PaginationUi: FC<PaginationUiProps> = ({ pager, onPageChange }) => {
const handlePageChange = (page: number) => {
onPageChange(page);
};
const getPageNumbers = () => {
if (!pager) return [];
const pageNumbers: (number | 'ellipsis')[] = [];
const maxVisiblePages = 5;
if (pager.totalPages <= maxVisiblePages) {
for (let i = 1; i <= pager.totalPages; i++) {
pageNumbers.push(i);
}
} else {
if (pager.page > 3) {
pageNumbers.push(1);
if (pager.page > 4) {
pageNumbers.push('ellipsis');
}
}
const start = Math.max(2, pager.page - 1);
const end = Math.min(pager.totalPages - 1, pager.page + 1);
for (let i = start; i <= end; i++) {
pageNumbers.push(i);
}
if (pager.page < pager.totalPages - 2) {
if (pager.page < pager.totalPages - 3) {
pageNumbers.push('ellipsis');
}
pageNumbers.push(pager.totalPages);
}
}
return pageNumbers;
};
if (!pager || pager.totalPages <= 1) {
return null;
}
return (
<div className="flex ی justify-center mt-6">
<Pagination>
<PaginationContent>
{getPageNumbers().map((page, index) => (
<PaginationItem key={index}>
{page === 'ellipsis' ? (
<PaginationEllipsis />
) : (
<PaginationLink
href="#"
isActive={page === pager.page}
onClick={(e) => {
e.preventDefault();
handlePageChange(page);
}}
>
{page}
</PaginationLink>
)}
</PaginationItem>
))}
</PaginationContent>
</Pagination>
</div>
);
};
export default PaginationUi;
+58
View File
@@ -0,0 +1,58 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
+127
View File
@@ -0,0 +1,127 @@
import * as React from "react"
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
}
function PaginationContent({
className,
...props
}: React.ComponentProps<"ul">) {
return (
<ul
data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
)
}
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
return <li data-slot="pagination-item" {...props} />
}
type PaginationLinkProps = {
isActive?: boolean
} & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a">
function PaginationLink({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
)
}
function PaginationPrevious({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
</PaginationLink>
)
}
function PaginationNext({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
{...props}
>
<span className="hidden sm:block">Next</span>
<ChevronRightIcon />
</PaginationLink>
)
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
)
}
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}