Skip to content

Instantly share code, notes, and snippets.

@alobato
Created February 20, 2019 18:41
Show Gist options
  • Save alobato/6779ccc4d6bbb74322266ffd4d1c2d2a to your computer and use it in GitHub Desktop.
Save alobato/6779ccc4d6bbb74322266ffd4d1c2d2a to your computer and use it in GitHub Desktop.
import React, { memo, useState, useEffect } from 'react'
import memoize from 'memoize-one'
import isEqual from 'lodash.isequal'
const equalFn = (newArg, lastArg) => isEqual(newArg, lastArg)
const range = (start, end) => [...Array(end - start).keys()].map(k => k + start)
const getPager = memoize((totalItems, currentPage, pageSize) => {
// calculate total pages
const totalPages = Math.ceil(totalItems / pageSize)
let startPage, endPage
if (totalPages <= 10) {
// less than 10 total pages so show all
startPage = 1
endPage = totalPages
} else {
// more than 10 total pages so calculate start and end pages
if (currentPage <= 6) {
startPage = 1
endPage = 10
} else if (currentPage + 4 >= totalPages) {
startPage = totalPages - 9
endPage = totalPages
} else {
startPage = currentPage - 5
endPage = currentPage + 4
}
}
// calculate start and end item indexes
const startIndex = (currentPage - 1) * pageSize
const endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1)
// create an array of pages
const pages = range(startPage, endPage + 1)
// return object with all pager properties required by the view
return {
totalItems: totalItems,
currentPage: currentPage,
pageSize: pageSize,
totalPages: totalPages,
startPage: startPage,
endPage: endPage,
startIndex: startIndex,
endIndex: endIndex,
pages: pages
}
})
const getPageOfItems = memoize((items, pager) => items.slice(pager.startIndex, pager.endIndex + 1), equalFn)
const Pagination = memo(({ initialPage, pageSize, items, previousLabel, nextLabel, fistLabel, lastLabel, onChange, ...rest }) => {
const processedItems = () => items
const genPager = () => getPager(processedItems().length, initialPage, pageSize)
const [pager, setPager] = useState(genPager())
useEffect(() => {
const newPager = genPager()
setPager(newPager)
const pageOfItems = getPageOfItems(processedItems(), newPager)
onChange(pageOfItems)
}, [JSON.stringify(items)]
)
const setPage = page => {
const newPager = getPager(processedItems().length, page, pageSize)
setPager(newPager)
const pageOfItems = getPageOfItems(processedItems(), newPager)
onChange(pageOfItems)
}
const isFirstPage = pager.currentPage === 1
const isCurrentPage = page => pager.currentPage === page
return (
<div {...rest}>
<ul>
<li className={isFirstPage ? 'disabled' : ''}>
{isFirstPage ? (<span>{fistLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(1)} onClick={() => setPage(1)}>{fistLabel}</span>)}
</li>
<li className={isFirstPage ? 'disabled' : ''}>
{isFirstPage ? (<span>{previousLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(pager.currentPage - 1)} onClick={() => setPage(pager.currentPage - 1)}>{previousLabel}</span>)}
</li>
{pager.pages.map((page, index) =>
<li key={index} className={isCurrentPage(page) ? 'active' : ''}><span tabIndex={isCurrentPage(page) ? -1 : 0} onKeyPress={e => (e.key === 'Enter') && setPage(page)} onClick={() => setPage(page)}>{page}</span></li>
)}
<li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
{pager.currentPage === pager.totalPages ? (<span>{nextLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(pager.currentPage + 1)} onClick={() => setPage(pager.currentPage + 1)}>{nextLabel}</span>)}
</li>
<li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
{pager.currentPage === pager.totalPages ? (<span>{lastLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(pager.totalPages)} onClick={() => setPage(pager.totalPages)}>{lastLabel}</span>)}
</li>
</ul>
</div>
)
}, equalFn)
Pagination.defaultProps = {
initialPage: 1,
pageSize: 20,
items: [],
fistLabel: 'Primeira',
nextLabel: 'Próxima',
previousLabel: 'Anterior',
lastLabel: 'Última',
onChange: () => {}
}
export default Pagination
import styled from 'styled-components'
import Pagination from './Pagination'
const StyledPagination = styled(Pagination)`
display: flex;
justify-content: center;
& > ul {
display: inline-block;
padding-left: 0;
border-radius: 4px;
user-select: none;
}
& > ul > li {
display:inline;
}
& > ul > li > a {
cursor: pointer;
}
& > ul > li > a, & > ul > li > span {
cursor: pointer;
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: hsla(216, 40%, 55%, 1);
text-decoration: none;
background-color: white;
border: 1px solid rgb(231, 231, 231);
}
& > ul > li:first-child > a, & ul > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
& > ul > li:last-child > a, & > ul > li:last-child > span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
& > ul > li > a:focus, & > ul > li > a:hover, & > ul > li > span:focus, & > ul > li > span:hover {
z-index: 3;
color: rgb(66, 133, 244);
background-color: rgb(240, 240, 240);
border-color: rgb(231, 231, 231);
}
& > ul > .active > a,
& > ul > .active > a:focus,
& > ul > .active > a:hover,
& > ul > .active > span,
& > ul > .active > span:focus,
& > ul > .active > span:hover {
z-index: 2;
color: white;
cursor: default;
background-color: hsla(216, 40%, 55%, 1);
border-color: hsla(216, 40%, 55%, 1);
}
& > ul > .disabled > a,
& > ul > .disabled > a:focus,
& > ul > .disabled > a:hover,
& > ul > .disabled > span,
& > ul > .disabled > span:focus,
& > ul > .disabled > span:hover {
color: rgb(200, 200, 200);
cursor: not-allowed;
background-color: white;
border-color: rgb(231, 231, 231);
}
`
export default StyledPagination
import { useState } from 'react'
function usePagination(items) {
const [currentPageItems, setCurrentPageItems] = useState([])
const paginationProps = { items: items, onChange: setCurrentPageItems }
return [paginationProps, currentPageItems]
}
export default usePagination
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment