pagination.tsx 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import {browserHistory, withRouter, WithRouterProps} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import {Query} from 'history';
  4. import Button from 'app/components/button';
  5. import ButtonBar from 'app/components/buttonBar';
  6. import {IconChevron} from 'app/icons';
  7. import {t} from 'app/locale';
  8. import space from 'app/styles/space';
  9. import parseLinkHeader from 'app/utils/parseLinkHeader';
  10. /**
  11. * @param cursor The string cursor value
  12. * @param path The current page pathname
  13. * @param query The current query object
  14. * @param delta The delta in page number change triggered by the
  15. * click. A negative delta would be a "previous" page.
  16. */
  17. export type CursorHandler = (
  18. cursor: string | undefined,
  19. path: string,
  20. query: Query,
  21. delta: number
  22. ) => void;
  23. type Props = WithRouterProps & {
  24. pageLinks?: string | null;
  25. to?: string;
  26. caption?: React.ReactElement;
  27. size?: 'zero' | 'xsmall' | 'small';
  28. onCursor?: CursorHandler;
  29. disabled?: boolean;
  30. className?: string;
  31. };
  32. const defaultOnCursor: CursorHandler = (cursor, path, query, _direction) =>
  33. browserHistory.push({
  34. pathname: path,
  35. query: {...query, cursor},
  36. });
  37. const Pagination = ({
  38. to,
  39. location,
  40. className,
  41. onCursor = defaultOnCursor,
  42. pageLinks,
  43. size = 'small',
  44. caption,
  45. disabled = false,
  46. }: Props) => {
  47. if (!pageLinks) {
  48. return null;
  49. }
  50. const path = to ?? location.pathname;
  51. const query = location.query;
  52. const links = parseLinkHeader(pageLinks);
  53. const previousDisabled = disabled || links.previous?.results === false;
  54. const nextDisabled = disabled || links.next?.results === false;
  55. return (
  56. <Wrapper className={className}>
  57. {caption && <PaginationCaption>{caption}</PaginationCaption>}
  58. <ButtonBar merged>
  59. <Button
  60. icon={<IconChevron direction="left" size="sm" />}
  61. aria-label={t('Previous')}
  62. size={size}
  63. disabled={previousDisabled}
  64. onClick={() => onCursor?.(links.previous?.cursor, path, query, -1)}
  65. />
  66. <Button
  67. icon={<IconChevron direction="right" size="sm" />}
  68. aria-label={t('Next')}
  69. size={size}
  70. disabled={nextDisabled}
  71. onClick={() => onCursor?.(links.next?.cursor, path, query, 1)}
  72. />
  73. </ButtonBar>
  74. </Wrapper>
  75. );
  76. };
  77. const Wrapper = styled('div')`
  78. display: flex;
  79. align-items: center;
  80. justify-content: flex-end;
  81. margin: ${space(3)} 0 0 0;
  82. `;
  83. const PaginationCaption = styled('span')`
  84. color: ${p => p.theme.subText};
  85. font-size: ${p => p.theme.fontSizeMedium};
  86. margin-right: ${space(2)};
  87. `;
  88. export default withRouter(Pagination);