vitess.io/vitess@v0.16.2/web/vtadmin/src/components/dataTable/PaginationNav.tsx (about) 1 /** 2 * Copyright 2021 The Vitess Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 import cx from 'classnames'; 17 import * as React from 'react'; 18 import { Link, LinkProps } from 'react-router-dom'; 19 20 import style from './PaginationNav.module.scss'; 21 22 export interface Props { 23 currentPage: number; 24 formatLink: (page: number) => LinkProps['to']; 25 // The maximum number of pagination elements to show. Note that this includes any placeholders. 26 // It's recommended for this value to be >= 5 to handle the case where there are 27 // breaks on either side of the list. 28 maxVisible?: number; 29 // The minimum number of pagination elements to show at the beginning/end of a sequence, 30 // adjacent to any sequence breaks. 31 minWidth?: number; 32 // The total number of pages 33 totalPages: number; 34 } 35 36 const DEFAULT_MAX_VISIBLE = 8; 37 const DEFAULT_MIN_WIDTH = 1; 38 39 // This assumes we always want to 1-index our pages, where "page 1" is the first page. 40 // If we find a need for zero-indexed pagination, we can make this configurable. 41 const FIRST_PAGE = 1; 42 43 // PageSpecifiers with a numeric value are links. `null` is used 44 // to signify a break in the sequence. 45 type PageSpecifier = number | null; 46 47 export const PaginationNav = ({ 48 currentPage, 49 formatLink, 50 maxVisible = DEFAULT_MAX_VISIBLE, 51 minWidth = DEFAULT_MIN_WIDTH, 52 totalPages, 53 }: Props) => { 54 if (totalPages <= 1) { 55 return null; 56 } 57 58 // This rather magical solution is borrowed, with gratitude, from StackOverflow 59 // https://stackoverflow.com/a/46385144 60 const leftWidth = (maxVisible - minWidth * 2 - 3) >> 1; 61 const rightWidth = (maxVisible - minWidth * 2 - 2) >> 1; 62 63 let numbers: PageSpecifier[] = []; 64 if (totalPages <= maxVisible) { 65 // No breaks in list 66 numbers = range(FIRST_PAGE, totalPages); 67 } else if (currentPage <= maxVisible - minWidth - 1 - rightWidth) { 68 // No break on left side of page 69 numbers = range(FIRST_PAGE, maxVisible - minWidth - 1).concat( 70 null, 71 range(totalPages - minWidth + 1, totalPages) 72 ); 73 } else if (currentPage >= totalPages - minWidth - 1 - rightWidth) { 74 // No break on right of page 75 numbers = range(FIRST_PAGE, minWidth).concat( 76 null, 77 range(totalPages - minWidth - 1 - rightWidth - leftWidth, totalPages) 78 ); 79 } else { 80 // Breaks on both sides 81 numbers = range(FIRST_PAGE, minWidth).concat( 82 null, 83 range(currentPage - leftWidth, currentPage + rightWidth), 84 null, 85 range(totalPages - minWidth + 1, totalPages) 86 ); 87 } 88 89 return ( 90 <nav> 91 <ul className={style.links}> 92 {numbers.map((num: number | null, idx) => 93 num === null ? ( 94 <li key={`placeholder-${idx}`}> 95 <div className={style.placeholder} /> 96 </li> 97 ) : ( 98 <li key={num}> 99 <Link 100 className={cx(style.link, { [style.activeLink]: num === currentPage })} 101 to={formatLink(num)} 102 > 103 {num} 104 </Link> 105 </li> 106 ) 107 )} 108 </ul> 109 </nav> 110 ); 111 }; 112 113 // lodash-es has a `range` function but it doesn't play nice 114 // with the PageSpecifier[] return type (since it's a mixed array 115 // of numbers and nulls). 116 const range = (start: number, end: number): PageSpecifier[] => { 117 if (isNaN(start) || isNaN(end)) return []; 118 return Array.from(Array(end - start + 1), (_, i) => i + start); 119 };