@primer/react
Version:
An implementation of GitHub's Primer Design System using React
200 lines (188 loc) • 5.52 kB
JavaScript
function buildPaginationModel(pageCount, currentPage, showPages, marginPageCount, surroundingPageCount) {
const prev = {
type: 'PREV',
num: currentPage - 1,
disabled: currentPage === 1
};
const next = {
type: 'NEXT',
num: currentPage + 1,
disabled: currentPage === pageCount
};
if (!showPages) {
return [prev, next];
}
if (pageCount <= 0) {
return [prev, {
...next,
disabled: true
}];
}
const pages = [];
// number of pages shown on each side of the current page
// [1, ..., 7, 8, _9_, 10, 11, ..., 15]
// standardGap: 3
const standardGap = surroundingPageCount + marginPageCount;
// the maximum number of pages that can be shown at a given time (account for current page, left and right ellipsis)
// [1, ..., 7, 8, _9_, 10, 11, ..., 15]
// maxVisiblePages: 9
const maxVisiblePages = standardGap + standardGap + 3;
// if the number of pages is less than the maximum number of pages that can be shown just return all of them
if (pageCount <= maxVisiblePages) {
addPages(1, pageCount, false);
return [prev, ...pages, next];
}
// startGap is the number of pages hidden by the start ellipsis
// startOffset is the number of pages to offset at the start to compensate
// [1, ..., 7, 8, _9_, 10, 11, ..., 15]
// startGap: 5
// startOffset: 0
// when the margin and the surrounding windows overlap.
// [1, _2_, 3, 4, 5, 6, ..., 15]
// startGap = 0
// startOffset: -3 <--
let startGap = 0;
let startOffset = 0;
// When there is overlap
if (currentPage - standardGap - 1 <= 1) {
startOffset = currentPage - standardGap - 2;
} else {
startGap = currentPage - standardGap - 1;
}
// These are equivalent to startGap and startOffset but at the end of the list
let endGap = 0;
let endOffset = 0;
// When there is overlap
if (pageCount - currentPage - standardGap <= 1) {
endOffset = pageCount - currentPage - standardGap - 1;
} else {
endGap = pageCount - currentPage - standardGap;
}
const hasStartEllipsis = startGap > 0;
const hasEndEllipsis = endGap > 0;
// add pages "before" the start ellipsis (if any)
// [1, ..., 7, 8, _9_, 10, 11, ..., 15]
// marginPageCount: 1
// addPages(1, 1, true)
addPages(1, marginPageCount, hasStartEllipsis);
if (hasStartEllipsis) {
addEllipsis(marginPageCount);
}
// add middle pages
// [1, ..., 7, 8, _9_, 10, 11, ..., 15]
// marginPageCount: 1
// surroundingPageCount: 2
// startGap: 5
// startOffset: 0
// endGap: 3
// endOffset: 0
// addPages(7, 11, true)
addPages(marginPageCount + startGap + endOffset + 1, pageCount - startOffset - endGap - marginPageCount, hasEndEllipsis);
if (hasEndEllipsis) {
addEllipsis(pageCount - startOffset - endGap - marginPageCount);
}
// add pages "after" the start ellipsis (if any)
// [1, ..., 7, 8, _9_, 10, 11, ..., 15]
// marginPageCount: 1
// surroundingPageCount: 2
// startGap: 5
// startOffset: 0
// endGap: 3
// endOffset: 0
// addPages(15, 15)
addPages(pageCount - marginPageCount + 1, pageCount);
return [prev, ...pages, next];
function addEllipsis(previousPage) {
pages.push({
type: 'BREAK',
num: previousPage + 1
});
}
function addPages(start, end, precedesBreak = false) {
for (let i = start; i <= end; i++) {
pages.push({
type: 'NUM',
num: i,
selected: i === currentPage,
precedesBreak: i === end && precedesBreak
});
}
}
}
function buildComponentData(page, hrefBuilder, onClick) {
const props = {};
let content = '';
let key = '';
switch (page.type) {
case 'PREV':
{
key = 'page-prev';
content = 'Previous';
if (page.disabled) {
Object.assign(props, {
rel: 'prev',
'aria-hidden': 'true',
'aria-disabled': 'true'
});
} else {
Object.assign(props, {
rel: 'prev',
href: hrefBuilder(page.num),
'aria-label': 'Previous Page',
onClick
});
}
break;
}
case 'NEXT':
{
key = 'page-next';
content = 'Next';
if (page.disabled) {
Object.assign(props, {
rel: 'next',
'aria-hidden': 'true',
'aria-disabled': 'true'
});
} else {
Object.assign(props, {
rel: 'next',
href: hrefBuilder(page.num),
'aria-label': 'Next Page',
onClick
});
}
break;
}
case 'NUM':
{
key = `page-${page.num}`;
content = String(page.num);
Object.assign(props, {
href: hrefBuilder(page.num),
// We append "..." to the aria-label for pages that preceed a break because screen readers will
// change the tone the text is read in.
// This is a slightly nicer experience than skipping a bunch of numbers unexpectedly.
'aria-label': `Page ${page.num}${page.precedesBreak ? '...' : ''}`,
onClick,
'aria-current': page.selected ? 'page' : undefined
});
break;
}
case 'BREAK':
{
key = `page-${page.num}-break`;
content = '…';
Object.assign(props, {
as: 'span',
role: 'presentation'
});
}
}
return {
props,
key,
content
};
}
export { buildComponentData, buildPaginationModel };