accessibilityTableCell.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import type {ComponentProps, CSSProperties, ReactNode} from 'react';
  2. import {forwardRef} from 'react';
  3. import styled from '@emotion/styled';
  4. import classNames from 'classnames';
  5. import {Cell, Text} from 'sentry/components/replays/virtualizedGrid/bodyCell';
  6. import TextOverflow from 'sentry/components/textOverflow';
  7. import {Tooltip} from 'sentry/components/tooltip';
  8. import {IconFire, IconInfo, IconWarning} from 'sentry/icons';
  9. import {space} from 'sentry/styles/space';
  10. import type useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  11. import type {HydratedA11yFrame} from 'sentry/utils/replays/hydrateA11yFrame';
  12. import useUrlParams from 'sentry/utils/useUrlParams';
  13. import type useSortAccessibility from 'sentry/views/replays/detail/accessibility/useSortAccessibility';
  14. const EMPTY_CELL = '--';
  15. interface Props extends ReturnType<typeof useCrumbHandlers> {
  16. a11yIssue: HydratedA11yFrame;
  17. columnIndex: number;
  18. currentHoverTime: number | undefined;
  19. currentTime: number;
  20. onClickCell: (props: {dataIndex: number; rowIndex: number}) => void;
  21. rowIndex: number;
  22. sortConfig: ReturnType<typeof useSortAccessibility>['sortConfig'];
  23. style: CSSProperties;
  24. }
  25. const AccessibilityTableCell = forwardRef<HTMLDivElement, Props>(
  26. (
  27. {
  28. a11yIssue,
  29. columnIndex,
  30. currentHoverTime,
  31. currentTime,
  32. onClickCell,
  33. onMouseEnter,
  34. onMouseLeave,
  35. rowIndex,
  36. sortConfig,
  37. style,
  38. }: Props,
  39. ref
  40. ) => {
  41. // Rows include the sortable header, the dataIndex does not
  42. const dataIndex = rowIndex - 1;
  43. const {getParamValue} = useUrlParams('a_detail_row', '');
  44. const isSelected = getParamValue() === String(dataIndex);
  45. const IMPACT_ICON_MAPPING: Record<keyof HydratedA11yFrame['impact'], ReactNode> = {
  46. minor: <IconInfo size="xs" />,
  47. moderate: <IconInfo size="xs" />,
  48. serious: <IconWarning size="xs" color={isSelected ? 'white' : 'yellow400'} />,
  49. critical: <IconFire size="xs" color={isSelected ? 'white' : 'red400'} />,
  50. };
  51. const hasOccurred = currentTime >= a11yIssue.offsetMs;
  52. const isBeforeHover =
  53. currentHoverTime === undefined || currentHoverTime >= a11yIssue.offsetMs;
  54. const isByTimestamp = sortConfig.by === 'timestampMs';
  55. const isAsc = isByTimestamp ? sortConfig.asc : undefined;
  56. const columnProps = {
  57. className: classNames({
  58. beforeCurrentTime: isByTimestamp
  59. ? isAsc
  60. ? hasOccurred
  61. : !hasOccurred
  62. : undefined,
  63. afterCurrentTime: isByTimestamp
  64. ? isAsc
  65. ? !hasOccurred
  66. : hasOccurred
  67. : undefined,
  68. beforeHoverTime:
  69. isByTimestamp && currentHoverTime !== undefined
  70. ? isAsc
  71. ? isBeforeHover
  72. : !isBeforeHover
  73. : undefined,
  74. afterHoverTime:
  75. isByTimestamp && currentHoverTime !== undefined
  76. ? isAsc
  77. ? !isBeforeHover
  78. : isBeforeHover
  79. : undefined,
  80. }),
  81. hasOccurred: isByTimestamp ? hasOccurred : undefined,
  82. isSelected,
  83. onClick: () => onClickCell({dataIndex, rowIndex}),
  84. onMouseEnter: () => onMouseEnter(a11yIssue),
  85. onMouseLeave: () => onMouseLeave(a11yIssue),
  86. ref,
  87. style,
  88. } as ComponentProps<typeof Cell>;
  89. const renderFns = [
  90. () => (
  91. <StyledCell {...columnProps} impact={a11yIssue.impact} isRowSelected={isSelected}>
  92. <Text>
  93. {a11yIssue.impact ? (
  94. <Tooltip title={a11yIssue.impact ?? EMPTY_CELL}>
  95. {IMPACT_ICON_MAPPING[a11yIssue.impact]}
  96. </Tooltip>
  97. ) : (
  98. EMPTY_CELL
  99. )}
  100. </Text>
  101. </StyledCell>
  102. ),
  103. () => (
  104. <StyledCell {...columnProps} impact={a11yIssue.impact} isRowSelected={isSelected}>
  105. <Text>{a11yIssue.id ?? EMPTY_CELL}</Text>
  106. </StyledCell>
  107. ),
  108. () => (
  109. <StyledCell {...columnProps} impact={a11yIssue.impact} isRowSelected={isSelected}>
  110. <Tooltip
  111. title={a11yIssue.element.element ?? EMPTY_CELL}
  112. isHoverable
  113. showOnlyOnOverflow
  114. overlayStyle={{maxWidth: '500px !important'}}
  115. >
  116. <StyledTextOverflow>
  117. {a11yIssue.element.element ?? EMPTY_CELL}
  118. </StyledTextOverflow>
  119. </Tooltip>
  120. </StyledCell>
  121. ),
  122. ];
  123. return renderFns[columnIndex]();
  124. }
  125. );
  126. export default AccessibilityTableCell;
  127. const StyledTextOverflow = styled(TextOverflow)`
  128. padding-right: ${space(1)};
  129. `;
  130. const StyledCell = styled(Cell)<{
  131. impact: HydratedA11yFrame['impact'];
  132. isRowSelected: boolean;
  133. }>`
  134. background: ${p =>
  135. p.isSelected
  136. ? p.theme.purple300
  137. : p.impact === 'serious'
  138. ? p.theme.yellow100
  139. : p.impact === 'critical'
  140. ? p.theme.red100
  141. : 'transparent'};
  142. `;