@primer/react
Version:
An implementation of GitHub's Primer Design System using React
197 lines (193 loc) • 5.29 kB
JavaScript
function buildPaginationModel(pageCount, currentPage, showPages, marginPageCount, surroundingPageCount) {
const pages = [];
if (showPages) {
const pageNums = [];
const addPage = n => {
if (n >= 1 && n <= pageCount) {
pageNums.push(n);
}
};
// Start by defining the window of pages to show around the current page.
// If the window goes off either edge, shift it until it fits.
let extentLeft = currentPage - surroundingPageCount;
let extentRight = currentPage + surroundingPageCount;
if (extentLeft < 1 && extentRight > pageCount) {
// Our window is larger than the entire range,
// so simply display every page.
extentLeft = 1;
extentRight = pageCount;
} else if (extentLeft < 1) {
while (extentLeft < 1) {
extentLeft++;
extentRight++;
}
} else if (extentRight > pageCount) {
while (extentRight > pageCount) {
extentLeft--;
extentRight--;
}
}
// Next, include the pages in the margins.
// If a margin page is already covered in the window,
// extend the window to the other direction.
for (let i = 1; i <= marginPageCount; i++) {
const leftPage = i;
const rightPage = pageCount - (i - 1);
if (leftPage >= extentLeft) {
extentRight++;
} else {
addPage(leftPage);
}
if (rightPage <= extentRight) {
extentLeft--;
} else {
addPage(rightPage);
}
}
for (let i = extentLeft; i <= extentRight; i++) {
addPage(i);
}
const sorted = pageNums.slice().sort((a, b) => a - b).filter((item, idx, ary) => !idx || item !== ary[idx - 1]);
for (let idx = 0; idx < sorted.length; idx++) {
const num = sorted[idx];
const selected = num === currentPage;
const last = sorted[idx - 1];
const next = sorted[idx + 1];
const lastDelta = num - last;
const nextDelta = num - next;
const precedesBreak = nextDelta !== -1;
if (idx === 0) {
if (num !== 1) {
// If the first page isn't page one,
// we need to add a break
pages.push({
type: 'BREAK',
num: 1
});
}
pages.push({
type: 'NUM',
num,
selected,
precedesBreak
});
} else {
if (lastDelta === 1) {
pages.push({
type: 'NUM',
num,
selected,
precedesBreak
});
} else {
// We skipped some, so add a break
pages.push({
type: 'BREAK',
num: num - 1
});
pages.push({
type: 'NUM',
num,
selected,
precedesBreak: false
});
}
}
}
const lastPage = pages[pages.length - 1];
if (lastPage.type === 'NUM' && lastPage.num !== pageCount) {
// The last page we rendered wasn't the actual last page,
// so we need an additional break
pages.push({
type: 'BREAK',
num: pageCount
});
}
}
const prev = {
type: 'PREV',
num: currentPage - 1,
disabled: currentPage === 1
};
const next = {
type: 'NEXT',
num: currentPage + 1,
disabled: currentPage === pageCount
};
return [prev, ...pages, next];
}
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, {
as: 'span',
'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, {
as: 'span',
'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 };