pagination.tsx 2.9 KB

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