sortableHeader.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import type {ReactNode} from 'react';
  2. import styled from '@emotion/styled';
  3. import Link from 'sentry/components/links/link';
  4. import QuestionTooltip from 'sentry/components/questionTooltip';
  5. import {IconArrow} from 'sentry/icons';
  6. import {space} from 'sentry/styles/space';
  7. import {trackAnalytics} from 'sentry/utils/analytics';
  8. import type {Sort} from 'sentry/utils/discover/fields';
  9. import {useLocation} from 'sentry/utils/useLocation';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import type {
  12. ReplayListLocationQuery,
  13. ReplayRecordNestedFieldName,
  14. } from 'sentry/views/replays/types';
  15. type NotSortable = {
  16. label: string;
  17. tooltip?: string | ReactNode;
  18. };
  19. type Sortable = {
  20. fieldName: ReplayRecordNestedFieldName;
  21. label: string;
  22. sort: undefined | Sort;
  23. tooltip?: string | ReactNode;
  24. };
  25. type Props = NotSortable | Sortable;
  26. function SortableHeader(props: Props) {
  27. const location = useLocation<ReplayListLocationQuery>();
  28. const organization = useOrganization();
  29. if (!('sort' in props) || !props.sort) {
  30. const {label, tooltip} = props;
  31. return (
  32. <Header>
  33. {label}
  34. {tooltip ? (
  35. <StyledQuestionTooltip size="xs" position="top" title={tooltip} isHoverable />
  36. ) : null}
  37. </Header>
  38. );
  39. }
  40. const {fieldName, label, sort, tooltip} = props;
  41. const arrowDirection = sort?.kind === 'asc' ? 'up' : 'down';
  42. const sortArrow = <IconArrow color="gray300" size="xs" direction={arrowDirection} />;
  43. return (
  44. <Header>
  45. <SortLink
  46. role="columnheader"
  47. aria-sort={
  48. sort?.field.endsWith(fieldName)
  49. ? sort?.kind === 'asc'
  50. ? 'ascending'
  51. : 'descending'
  52. : 'none'
  53. }
  54. onClick={() => {
  55. const column = sort?.field.endsWith(fieldName)
  56. ? sort?.kind === 'desc'
  57. ? fieldName
  58. : '-' + fieldName
  59. : '-' + fieldName;
  60. trackAnalytics('replay.list-sorted', {
  61. organization,
  62. column,
  63. });
  64. }}
  65. to={{
  66. pathname: location.pathname,
  67. query: {
  68. ...location.query,
  69. sort: sort?.field.endsWith(fieldName)
  70. ? sort?.kind === 'desc'
  71. ? fieldName
  72. : '-' + fieldName
  73. : '-' + fieldName,
  74. },
  75. }}
  76. >
  77. {label} {sort?.field === fieldName && sortArrow}
  78. </SortLink>
  79. {tooltip ? (
  80. <StyledQuestionTooltip size="xs" position="top" title={tooltip} isHoverable />
  81. ) : null}
  82. </Header>
  83. );
  84. }
  85. const Header = styled('div')`
  86. display: grid;
  87. grid-template-columns: repeat(2, max-content);
  88. align-items: center;
  89. padding: ${space(1.5)};
  90. `;
  91. const SortLink = styled(Link)`
  92. color: inherit;
  93. :hover {
  94. color: inherit;
  95. }
  96. svg {
  97. vertical-align: top;
  98. }
  99. `;
  100. const StyledQuestionTooltip = styled(QuestionTooltip)`
  101. margin-left: ${space(0.5)};
  102. `;
  103. export default SortableHeader;