-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create pagination on home page proposals list
- Loading branch information
Showing
5 changed files
with
274 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { | ||
Button, | ||
Flex, | ||
HStack, | ||
Icon, | ||
Menu, | ||
MenuButton, | ||
MenuItem, | ||
MenuList, | ||
Text, | ||
} from '@chakra-ui/react'; | ||
import { | ||
CaretDoubleLeft, | ||
CaretDoubleRight, | ||
CaretDown, | ||
CaretLeft, | ||
CaretRight, | ||
} from '@phosphor-icons/react'; | ||
import { ComponentType } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { NEUTRAL_2_82_TRANSPARENT } from '../../../constants/common'; | ||
import { PAGE_SIZE_OPTIONS } from '../../../hooks/utils/usePagination'; | ||
import { EaseOutComponent } from './EaseOutComponent'; | ||
|
||
interface NavButtonProps { | ||
onClick: () => void; | ||
isDisabled: boolean; | ||
icon: ComponentType; | ||
} | ||
|
||
function NavButton({ onClick, isDisabled, icon: IconComponent }: NavButtonProps) { | ||
return ( | ||
<Button | ||
onClick={onClick} | ||
isDisabled={isDisabled} | ||
variant="tertiary" | ||
size="sm" | ||
> | ||
<IconComponent /> | ||
</Button> | ||
); | ||
} | ||
|
||
interface PaginationControlsProps { | ||
currentPage: number; | ||
totalPages: number; | ||
onPageChange: (page: number) => void; | ||
pageSize: number; | ||
onPageSizeChange: (size: number) => void; | ||
} | ||
|
||
export function PaginationControls({ | ||
currentPage, | ||
totalPages, | ||
onPageChange, | ||
pageSize, | ||
onPageSizeChange, | ||
}: PaginationControlsProps) { | ||
const { t } = useTranslation(['common']); | ||
|
||
return ( | ||
<Flex | ||
align="center" | ||
gap={2} | ||
> | ||
<Menu isLazy> | ||
<MenuButton | ||
as={Button} | ||
variant="tertiary" | ||
size="sm" | ||
> | ||
<Flex alignItems="center"> | ||
<Text fontSize="sm">{pageSize}</Text> | ||
<Icon | ||
ml="0.25rem" | ||
as={CaretDown} | ||
/> | ||
</Flex> | ||
</MenuButton> | ||
<MenuList | ||
borderWidth="1px" | ||
borderColor="neutral-3" | ||
borderRadius="0.75rem" | ||
bg={NEUTRAL_2_82_TRANSPARENT} | ||
backdropFilter="auto" | ||
backdropBlur="10px" | ||
minWidth="min-content" | ||
zIndex={5} | ||
p="0.25rem" | ||
> | ||
<EaseOutComponent> | ||
{PAGE_SIZE_OPTIONS.map(size => ( | ||
<MenuItem | ||
key={size} | ||
borderRadius="0.75rem" | ||
p="0.5rem 0.5rem" | ||
sx={{ | ||
'&:hover': { bg: 'neutral-3' }, | ||
}} | ||
onClick={() => onPageSizeChange(size)} | ||
> | ||
<Text fontSize="sm">{size}</Text> | ||
</MenuItem> | ||
))} | ||
</EaseOutComponent> | ||
</MenuList> | ||
</Menu> | ||
|
||
<HStack spacing={1}> | ||
<NavButton | ||
onClick={() => onPageChange(1)} | ||
isDisabled={currentPage === 1} | ||
icon={CaretDoubleLeft} | ||
/> | ||
<NavButton | ||
onClick={() => onPageChange(currentPage - 1)} | ||
isDisabled={currentPage === 1} | ||
icon={CaretLeft} | ||
/> | ||
|
||
<Text | ||
fontSize="sm" | ||
px={2} | ||
color="lilac-0" | ||
> | ||
{t('pageXofY', { current: currentPage, total: totalPages })} | ||
</Text> | ||
|
||
<NavButton | ||
onClick={() => onPageChange(currentPage + 1)} | ||
isDisabled={currentPage === totalPages} | ||
icon={CaretRight} | ||
/> | ||
<NavButton | ||
onClick={() => onPageChange(totalPages)} | ||
isDisabled={currentPage === totalPages} | ||
icon={CaretDoubleRight} | ||
/> | ||
</HStack> | ||
</Flex> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { useSearchParams } from 'react-router-dom'; | ||
|
||
// Only exported for PaginationControls component | ||
export const PAGE_SIZE_OPTIONS = [5, 10, 25, 50, 100]; | ||
const DEFAULT_PAGE_SIZE = 10; | ||
|
||
interface UsePaginationProps { | ||
totalItems: number; | ||
} | ||
|
||
const QUERY_PARAMS = { | ||
PAGE: 'page', | ||
SIZE: 'size', | ||
} as const; | ||
|
||
export function usePagination({ totalItems }: UsePaginationProps) { | ||
const [searchParams, setSearchParams] = useSearchParams(); | ||
|
||
const [currentPage, setCurrentPage] = useState(() => { | ||
const page = searchParams.get(QUERY_PARAMS.PAGE); | ||
return page ? parseInt(page) : 1; | ||
}); | ||
|
||
const [pageSize, setPageSize] = useState(() => { | ||
const size = searchParams.get(QUERY_PARAMS.SIZE); | ||
return size && PAGE_SIZE_OPTIONS.includes(parseInt(size)) ? parseInt(size) : DEFAULT_PAGE_SIZE; | ||
}); | ||
|
||
// Calculate total pages | ||
const totalPages = Math.ceil(totalItems / pageSize); | ||
|
||
// Update URL when state changes | ||
useEffect(() => { | ||
const newParams = new URLSearchParams(searchParams); | ||
newParams.set(QUERY_PARAMS.PAGE, currentPage.toString()); | ||
newParams.set(QUERY_PARAMS.SIZE, pageSize.toString()); | ||
setSearchParams(newParams, { replace: true }); | ||
}, [currentPage, pageSize, searchParams, setSearchParams]); | ||
|
||
// Handle page size changes | ||
const handlePageSizeChange = (newSize: number) => { | ||
setPageSize(newSize); | ||
setCurrentPage(1); | ||
}; | ||
|
||
// Calculate paginated items | ||
const getPaginatedItems = <T>(items: T[]) => { | ||
const startIndex = (currentPage - 1) * pageSize; | ||
return items.slice(startIndex, startIndex + pageSize); | ||
}; | ||
|
||
return { | ||
currentPage, | ||
setCurrentPage, | ||
pageSize, | ||
setPageSize: handlePageSizeChange, | ||
totalPages, | ||
getPaginatedItems, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters