Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 페이지네이션 컴포넌트 구현 #75

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/assets/svgs/icn_linearrow_small_left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/svgs/icn_linearrow_small_right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions src/components/common/button/pageBtn/PageBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import pageBtnStyles from './pageBtn.css';
interface PageBtnProps {
pageIndex: number;
currentPageNum: number;
onClick: () => void;
}

const PageBtn = ({ pageIndex, currentPageNum }: PageBtnProps) => {
const PageBtn = ({ pageIndex, currentPageNum, onClick }: PageBtnProps) => {
return (
<button
className={pageIndex === currentPageNum ? pageBtnStyles.current : pageBtnStyles.default}>
className={pageIndex === currentPageNum ? pageBtnStyles.current : pageBtnStyles.default}
onClick={onClick}>
{pageIndex}
</button>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/button/pageBtn/pageBtn.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import theme from '@styles/theme.css';
import { styleVariants } from '@vanilla-extract/css';

const commonPageNumStyles = {
width: '2.6rem',
height: '2.6rem',
width: '2.5rem',
height: '2.5rem',
...theme.FONTS.c2R14,
};

Expand Down
62 changes: 62 additions & 0 deletions src/components/common/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Icon from '@assets/svgs';
import React from 'react';

import * as styles from './pagination.css';
import PageBtn from '../button/pageBtn/PageBtn';

interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
color: 'gray' | 'white';
}

const Pagination = ({ currentPage, totalPages, onPageChange, color }: PaginationProps) => {
const calculatePageRange = () => {
const rangeStart = Math.floor((currentPage - 1) / 5) * 5 + 1;
const rangeEnd = Math.min(rangeStart + 4, totalPages);
return { rangeStart, rangeEnd };
};

const { rangeStart, rangeEnd } = calculatePageRange();

const isLeftDisabled = rangeStart === 1;
const isRightDisabled = rangeEnd === totalPages;

const renderPageNumbers = () => {
const pages = Array.from({ length: rangeEnd - rangeStart + 1 }, (_, i) => rangeStart + i);

return pages.map((page) => (
<PageBtn
key={page}
pageIndex={page}
currentPageNum={currentPage}
onClick={() => onPageChange(page)}
/>
));
};

return (
<nav className={`${styles.paginationContainer} ${styles.containerColors[color]}`}>
<button
className={styles.leftArrowStyle}
onClick={() => onPageChange(Math.max(1, rangeStart - 5))}
disabled={isLeftDisabled}>
<Icon.IcnLineArrowSmallLeft
className={isLeftDisabled ? styles.disabledIcon : styles.iconStyle}
/>
</button>
{renderPageNumbers()}
<button
className={styles.rightArrowStyle}
onClick={() => onPageChange(Math.min(totalPages, rangeStart + 5))}
disabled={isRightDisabled}>
<Icon.IcnLineArrowSmallRight
className={isRightDisabled ? styles.disabledIcon : styles.iconStyle}
/>
</button>
</nav>
);
};

export default Pagination;
61 changes: 61 additions & 0 deletions src/components/common/pagination/pagination.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import theme from '@styles/theme.css';
import { style, styleVariants } from '@vanilla-extract/css';

export const paginationContainer = style({
display: 'flex',
alignItems: 'center',
gap: '0.3rem',
height: '3.7rem',
padding: '0.6rem 0.8rem',
borderRadius: '2.4rem',
});

export const containerColors = styleVariants({
gray: {
backgroundColor: theme.COLORS.gray1,
},
white: {
backgroundColor: theme.COLORS.white,
},
});

export const leftArrowStyle = style({
width: '3.6rem',
paddingLeft: '0.4rem',
paddingRight: '1,6rem',
display: 'flex',
selectors: {
'&:disabled': {
cursor: 'not-allowed',
},
},
});

export const rightArrowStyle = style({
width: '3.6rem',
paddingLeft: '1.6rem',
paddingRight: '0.4rem',
display: 'flex',
selectors: {
'&:disabled': {
cursor: 'not-allowed',
},
},
});

export const iconStyle = style({
color: theme.COLORS.gray10,
transition: 'color 0.2s ease-in-out',
});

export const disabledIcon = style({
color: theme.COLORS.gray4,
});

export const dotStyle = style({
width: '2.6rem',
height: '2.6rem',
padding: '0 0.8rem',
...theme.FONTS.c2R14,
color: theme.COLORS.gray10,
});
92 changes: 92 additions & 0 deletions src/stories/Pagination.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Pagination from '@components/common/pagination/Pagination';
import type { Meta, StoryObj } from '@storybook/react';
import React, { useState } from 'react';

const meta: Meta<typeof Pagination> = {
title: 'Components/Pagination',
component: Pagination,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
currentPage: {
control: { type: 'number' },
description: 'Currently active page',
},
totalPages: {
control: { type: 'number' },
description: 'Total number of pages',
},
onPageChange: { action: 'Page changed' },
color: {
control: { type: 'inline-radio', options: ['gray', 'white'] },
description: 'Background color of the pagination container',
},
},
args: {
currentPage: 1,
totalPages: 13,
color: 'gray',
},
};

export default meta;
type Story = StoryObj<typeof Pagination>;

const PaginationExample = (props: {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
color: 'gray' | 'white';
}) => {
const { onPageChange } = props;
const [currentPage, setCurrentPage] = useState(props.currentPage);

const handlePageChange = (page: number) => {
setCurrentPage(page);
onPageChange?.(page);
};

return (
<Pagination
{...props}
currentPage={currentPage}
onPageChange={handlePageChange}
color={props.color}
/>
);
};

export const Default: Story = {
args: {
currentPage: 1,
totalPages: 13,
color: 'white',
},
render: (args) => {
return <PaginationExample {...args} />;
},
};

export const TotalOne: Story = {
args: {
currentPage: 13,
totalPages: 20,
color: 'white',
},
render: (args) => {
return <PaginationExample {...args} />;
},
};

export const TotalThree: Story = {
args: {
currentPage: 1,
totalPages: 3,
color: 'white',
},
render: (args) => {
return <PaginationExample {...args} />;
},
};
Loading