go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/components/params_pager/params_pager.tsx (about)

     1  // Copyright 2024 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import { Box, Button, ToggleButton, ToggleButtonGroup } from '@mui/material';
    16  import { useState } from 'react';
    17  import { Link } from 'react-router-dom';
    18  
    19  import { useSyncedSearchParams } from '@/generic_libs/hooks/synced_search_params';
    20  
    21  import {
    22    getPageSize,
    23    getPageToken,
    24    pageSizeUpdater,
    25    pageTokenUpdater,
    26  } from './params_pager_utils';
    27  
    28  export interface ParamsPagerProps {
    29    readonly nextPageToken: string;
    30  }
    31  
    32  /**
    33   * ParamsPager shows a page size and a next/previous page buttons.
    34   * This assumes an AIP-158 style pagination API (i.e. pageSize and pageToken fields)
    35   * This component does not emit events for updates, but instead stores the current
    36   * page and pageSize in the URL.
    37   * For the previous page button to work correctly you need to take care to not remount
    38   * the component each time you navigate to a page.  E.g. loading spinners must not
    39   * replace this component, but must be lower in the component hierarchy.
    40   */
    41  export function ParamsPager({ nextPageToken }: ParamsPagerProps) {
    42    const [searchParams, _] = useSyncedSearchParams();
    43    const pageSize = getPageSize(searchParams);
    44    const pageToken = getPageToken(searchParams);
    45  
    46    // There could be a lot of prev pages. Do not keep those tokens in the URL.
    47    const [prevPageTokens, setPrevPageTokens] = useState(() => {
    48      // If there's a page token when the component is FIRST INITIALIZED, allow
    49      // users to go back to the first page by inserting a blank page token.
    50      return pageToken ? [''] : [];
    51    });
    52  
    53    const prevPageToken = prevPageTokens.length
    54      ? prevPageTokens[prevPageTokens.length - 1]
    55      : null;
    56    return (
    57      <>
    58        <Box sx={{ mt: '5px' }}>
    59          Page Size:{' '}
    60          <ToggleButtonGroup exclusive value={pageSize} size="small">
    61            {[25, 50, 100, 200].map((s) => (
    62              <ToggleButton
    63                key={s}
    64                component={Link}
    65                to={`?${pageSizeUpdater(s)(searchParams)}`}
    66                value={s}
    67              >
    68                {s}
    69              </ToggleButton>
    70            ))}
    71          </ToggleButtonGroup>{' '}
    72          <Button
    73            disabled={prevPageToken === null}
    74            component={Link}
    75            to={`?${pageTokenUpdater(prevPageToken || '')(searchParams)}`}
    76            onClick={(e) => {
    77              if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
    78                return;
    79              }
    80              setPrevPageTokens(prevPageTokens.slice(0, -1));
    81            }}
    82          >
    83            Previous Page
    84          </Button>
    85          <Button
    86            disabled={nextPageToken === ''}
    87            component={Link}
    88            to={`?${pageTokenUpdater(nextPageToken)(searchParams)}`}
    89            onClick={(e) => {
    90              if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
    91                return;
    92              }
    93              setPrevPageTokens([...prevPageTokens, pageToken]);
    94            }}
    95          >
    96            Next Page
    97          </Button>
    98        </Box>
    99      </>
   100    );
   101  }