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  };