mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
Merge pull request #4202 from overleaf/jel-gallery-pagination
Add pagination to the gallery GitOrigin-RevId: 7107133da5e5ccf316235a6688070203c8bfa566
This commit is contained in:
@@ -117,6 +117,9 @@
|
||||
"github_too_many_files_error": "",
|
||||
"github_validation_check": "",
|
||||
"give_feedback": "",
|
||||
"go_next_page": "",
|
||||
"go_page": "",
|
||||
"go_prev_page": "",
|
||||
"go_to_error_location": "",
|
||||
"have_an_extra_backup": "",
|
||||
"headers": "",
|
||||
@@ -211,6 +214,8 @@
|
||||
"other_logs_and_files": "",
|
||||
"other_output_files": "",
|
||||
"owner": "",
|
||||
"page_current": "",
|
||||
"pagination_navigation": "",
|
||||
"pdf_compile_in_progress_error": "",
|
||||
"pdf_compile_rate_limit_hit": "",
|
||||
"pdf_compile_try_again": "",
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classNames from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
function Pagination({ currentPage, totalPages, handlePageClick }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const maxOtherPageButtons = useMemo(() => {
|
||||
let maxOtherPageButtons = 4 // does not include current page, prev/next buttons
|
||||
if (totalPages < maxOtherPageButtons + 1) {
|
||||
maxOtherPageButtons = totalPages - 1
|
||||
}
|
||||
return maxOtherPageButtons
|
||||
}, [totalPages])
|
||||
|
||||
const pageButtons = useMemo(() => {
|
||||
const result = []
|
||||
let nextPage = currentPage + 1
|
||||
let prevPage = currentPage - 1
|
||||
|
||||
function calcPages() {
|
||||
if (nextPage && nextPage <= totalPages) {
|
||||
result.push(nextPage)
|
||||
nextPage++
|
||||
} else {
|
||||
nextPage = undefined
|
||||
}
|
||||
|
||||
if (prevPage && prevPage > 0) {
|
||||
result.push(prevPage)
|
||||
prevPage--
|
||||
} else {
|
||||
prevPage = undefined
|
||||
}
|
||||
}
|
||||
|
||||
while (result.length < maxOtherPageButtons) {
|
||||
calcPages()
|
||||
}
|
||||
|
||||
result.push(currentPage) // wait until prev/next calculated to add current
|
||||
result.sort((a, b) => a - b) // sort numerically
|
||||
|
||||
return result
|
||||
}, [currentPage, totalPages, maxOtherPageButtons])
|
||||
|
||||
const morePrevPages = useMemo(() => {
|
||||
return pageButtons[0] !== 1 && currentPage - maxOtherPageButtons / 2 > 1
|
||||
}, [pageButtons, currentPage, maxOtherPageButtons])
|
||||
|
||||
const moreNextPages = useMemo(() => {
|
||||
return pageButtons[pageButtons.length - 1] < totalPages
|
||||
}, [pageButtons, totalPages])
|
||||
|
||||
return (
|
||||
<nav role="navigation" aria-label={t('pagination_navigation')}>
|
||||
<ul className="pagination">
|
||||
{currentPage > 1 && (
|
||||
<li>
|
||||
<button
|
||||
onClick={event => handlePageClick(event, currentPage - 1)}
|
||||
aria-label={t('go_prev_page')}
|
||||
>
|
||||
«
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{morePrevPages && (
|
||||
<li>
|
||||
<span className="ellipses">…</span>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{pageButtons.map(page => (
|
||||
<PaginationItem
|
||||
key={`prev-page-${page}`}
|
||||
page={page}
|
||||
currentPage={currentPage}
|
||||
handlePageClick={handlePageClick}
|
||||
/>
|
||||
))}
|
||||
|
||||
{moreNextPages && (
|
||||
<li>
|
||||
<span className="ellipses">…</span>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{currentPage < totalPages && (
|
||||
<li>
|
||||
<button
|
||||
onClick={event => handlePageClick(event, currentPage + 1)}
|
||||
aria-label={t('go_next_page')}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
function PaginationItem({ page, currentPage, handlePageClick }) {
|
||||
const { t } = useTranslation()
|
||||
const itemClassName = classNames({ active: currentPage === page })
|
||||
const ariaCurrent = currentPage === page
|
||||
const ariaLabel =
|
||||
currentPage === page ? t('page_current', { page }) : t('go_page', { page })
|
||||
return (
|
||||
<li className={itemClassName}>
|
||||
<button
|
||||
aria-current={ariaCurrent}
|
||||
onClick={event => handlePageClick(event, page)}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function isPositiveNumber(value) {
|
||||
return typeof value === 'number' && value > 0
|
||||
}
|
||||
|
||||
function isCurrentPageWithinTotalPages(currentPage, totalPages) {
|
||||
return currentPage <= totalPages
|
||||
}
|
||||
|
||||
Pagination.propTypes = {
|
||||
currentPage: function (props, propName, componentName) {
|
||||
if (
|
||||
!isPositiveNumber(props[propName]) ||
|
||||
!isCurrentPageWithinTotalPages(props.currentPage, props.totalPages)
|
||||
) {
|
||||
return new Error(
|
||||
'Invalid prop `' +
|
||||
propName +
|
||||
'` supplied to' +
|
||||
' `' +
|
||||
componentName +
|
||||
'`. Validation failed.'
|
||||
)
|
||||
}
|
||||
},
|
||||
totalPages: function (props, propName, componentName) {
|
||||
if (!isPositiveNumber(props[propName])) {
|
||||
return new Error(
|
||||
'Invalid prop `' +
|
||||
propName +
|
||||
'` supplied to' +
|
||||
' `' +
|
||||
componentName +
|
||||
'`. Validation failed.'
|
||||
)
|
||||
}
|
||||
},
|
||||
handlePageClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
PaginationItem.propTypes = {
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
handlePageClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Pagination
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
import Pagination from '../js/shared/components/pagination'
|
||||
|
||||
export const Interactive = args => {
|
||||
return <Pagination {...args} />
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Pagination',
|
||||
component: Pagination,
|
||||
args: {
|
||||
currentPage: 1,
|
||||
totalPages: 10,
|
||||
handlePageClick: () => {},
|
||||
},
|
||||
argTypes: {
|
||||
currentPage: { control: { type: 'number', min: 1, max: 10, step: 1 } },
|
||||
totalPages: { control: { disable: true } },
|
||||
},
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
> li {
|
||||
display: inline; // Remove list-style and block-level defaults
|
||||
> a,
|
||||
> button,
|
||||
> span {
|
||||
position: relative;
|
||||
float: left; // Collapse white-space
|
||||
@@ -23,6 +24,7 @@
|
||||
}
|
||||
&:first-child {
|
||||
> a,
|
||||
> button,
|
||||
> span {
|
||||
margin-left: 0;
|
||||
.border-left-radius(@border-radius-base);
|
||||
@@ -30,6 +32,7 @@
|
||||
}
|
||||
&:last-child {
|
||||
> a,
|
||||
> button,
|
||||
> span {
|
||||
.border-right-radius(@border-radius-base);
|
||||
}
|
||||
@@ -37,6 +40,7 @@
|
||||
}
|
||||
|
||||
> li > a,
|
||||
> li > button,
|
||||
> li > span {
|
||||
&:hover,
|
||||
&:focus {
|
||||
@@ -47,6 +51,7 @@
|
||||
}
|
||||
|
||||
> .active > a,
|
||||
> .active > button,
|
||||
> .active > span {
|
||||
&,
|
||||
&:hover,
|
||||
@@ -65,13 +70,20 @@
|
||||
> span:focus,
|
||||
> a,
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:focus,
|
||||
> button,
|
||||
> button:hover,
|
||||
> button:focus {
|
||||
color: @pagination-disabled-color;
|
||||
background-color: @pagination-disabled-bg;
|
||||
border-color: @pagination-disabled-border;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.ellipses {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Sizing
|
||||
|
||||
Reference in New Issue
Block a user