diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 6bc3f87d..146beb3e 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -26,7 +26,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.2", - "react-icons": "^5.3.0" + "react-icons": "^5.3.0", "zod": "^3.23.8" }, "devDependencies": { diff --git a/apps/frontend/src/feature/Pagination/usePagination.tsx b/apps/frontend/src/feature/Pagination/usePagination.tsx new file mode 100644 index 00000000..1be14e19 --- /dev/null +++ b/apps/frontend/src/feature/Pagination/usePagination.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; + +interface UsePaginationProps { + totalPages: number; + initialPage?: number; + onChangePage?: (page: number) => void; +} + +export function usePagination({ totalPages, initialPage = 1, onChangePage }: UsePaginationProps) { + const [currentPage, setCurrentPage] = useState(initialPage); + + const onClickPage = (page: number) => { + setCurrentPage(page); + onChangePage?.(page); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + const onClickPrevious = () => { + if (currentPage > 1) onClickPage(currentPage - 1); + }; + + const onClickNext = () => { + if (currentPage < totalPages) onClickPage(currentPage + 1); + }; + + // "첫 페이지 ... 현재 페이지와 앞뒤 1페이지 ... 마지막 페이지 "로 구성되도록 구현함 + const getPaginationItems = () => { + const items: (number | 'ellipsis')[] = []; + const showStartEllipsis = currentPage > 4; + const showEndEllipsis = currentPage < totalPages - 3; + + items.push(1); + + if (showStartEllipsis) items.push('ellipsis'); + + const startPage = showStartEllipsis ? Math.max(2, currentPage - 1) : 2; + const endPage = showEndEllipsis ? Math.min(totalPages - 1, currentPage + 1) : totalPages - 1; + + for (let page = startPage; page <= endPage; page++) { + items.push(page); + } + + if (showEndEllipsis) items.push('ellipsis'); + + if (totalPages > 1) items.push(totalPages); + + return items; + }; + + return { + currentPage, + onClickPage, + onClickPrevious, + onClickNext, + getPaginationItems + }; +} diff --git a/apps/frontend/src/widget/LotusList/LotusCardList.tsx b/apps/frontend/src/widget/LotusList/LotusCardList.tsx index 2cd0d0b0..20afcb46 100644 --- a/apps/frontend/src/widget/LotusList/LotusCardList.tsx +++ b/apps/frontend/src/widget/LotusList/LotusCardList.tsx @@ -1,5 +1,6 @@ import { Lotus } from '@/feature/Lotus'; import { LotusType } from '@/feature/Lotus/type'; +import { Pagination } from '@/widget/Pagination'; const LotusDummyData: LotusType = { link: 'https://example.com', @@ -14,29 +15,33 @@ const LotusDummyData: LotusType = { } }; +// TODO: 나중에 Props로 size 받아서 재사용하기 export function LotusCardList() { return ( -
- {new Array(10).fill(0).map((_, index) => ( - - - - -
- - -
- {LotusDummyData?.tags?.length ? ( - <> -
- - - ) : ( - <> - )} - - - ))} -
+ <> +
+ {new Array(10).fill(0).map((_, index) => ( + + + + +
+ + +
+ {LotusDummyData?.tags?.length ? ( + <> +
+ + + ) : ( + <> + )} + + + ))} +
+ console.log(page)} /> + ); } diff --git a/apps/frontend/src/widget/Pagination.tsx b/apps/frontend/src/widget/Pagination.tsx new file mode 100644 index 00000000..7bbd470e --- /dev/null +++ b/apps/frontend/src/widget/Pagination.tsx @@ -0,0 +1,48 @@ +import { + Pagination as PaginationBox, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious +} from '@froxy/design/components'; +import { usePagination } from '@/feature/Pagination/usePagination'; + +interface PaginationProps { + totalPages: number; + initialPage?: number; + onChangePage?: (page: number) => void; +} + +export function Pagination({ totalPages, initialPage = 1, onChangePage }: PaginationProps) { + const { currentPage, onClickPage, onClickPrevious, onClickNext, getPaginationItems } = usePagination({ + totalPages, + initialPage, + onChangePage + }); + + return ( + + + + + + {getPaginationItems().map((item, index) => ( + + {typeof item === 'number' ? ( + item !== currentPage && onClickPage(item)} isActive={item === currentPage}> + {item} + + ) : ( + + )} + + ))} + + + + + + ); +} diff --git a/packages/design/src/components/index.tsx b/packages/design/src/components/index.tsx index 86afb753..fac30e49 100644 --- a/packages/design/src/components/index.tsx +++ b/packages/design/src/components/index.tsx @@ -2,6 +2,7 @@ export * from './ui/badge'; export * from './ui/button'; export * from './ui/carousel'; export * from './ui/input'; +export * from './ui/pagination'; export * from './ui/typography'; export * from './ui/tabs'; export * from './Slot'; diff --git a/packages/design/src/components/ui/pagination.tsx b/packages/design/src/components/ui/pagination.tsx new file mode 100644 index 00000000..5be67417 --- /dev/null +++ b/packages/design/src/components/ui/pagination.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'; +import { ButtonProps, buttonVariants } from './button'; +import { cn } from '@/lib/utils'; + +const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => ( +