mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
import type {ComponentProps, CSSProperties, ReactNode} from 'react';
|
|
import {forwardRef} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import classNames from 'classnames';
|
|
|
|
import {Cell, Text} from 'sentry/components/replays/virtualizedGrid/bodyCell';
|
|
import TextOverflow from 'sentry/components/textOverflow';
|
|
import {Tooltip} from 'sentry/components/tooltip';
|
|
import {IconFire, IconInfo, IconWarning} from 'sentry/icons';
|
|
import {space} from 'sentry/styles/space';
|
|
import type useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
|
|
import type {HydratedA11yFrame} from 'sentry/utils/replays/hydrateA11yFrame';
|
|
import useUrlParams from 'sentry/utils/useUrlParams';
|
|
import type useSortAccessibility from 'sentry/views/replays/detail/accessibility/useSortAccessibility';
|
|
|
|
const EMPTY_CELL = '--';
|
|
|
|
interface Props extends ReturnType<typeof useCrumbHandlers> {
|
|
a11yIssue: HydratedA11yFrame;
|
|
columnIndex: number;
|
|
currentHoverTime: number | undefined;
|
|
currentTime: number;
|
|
onClickCell: (props: {dataIndex: number; rowIndex: number}) => void;
|
|
rowIndex: number;
|
|
sortConfig: ReturnType<typeof useSortAccessibility>['sortConfig'];
|
|
style: CSSProperties;
|
|
}
|
|
|
|
const AccessibilityTableCell = forwardRef<HTMLDivElement, Props>(
|
|
(
|
|
{
|
|
a11yIssue,
|
|
columnIndex,
|
|
currentHoverTime,
|
|
currentTime,
|
|
onClickCell,
|
|
onMouseEnter,
|
|
onMouseLeave,
|
|
rowIndex,
|
|
sortConfig,
|
|
style,
|
|
}: Props,
|
|
ref
|
|
) => {
|
|
// Rows include the sortable header, the dataIndex does not
|
|
const dataIndex = rowIndex - 1;
|
|
|
|
const {getParamValue} = useUrlParams('a_detail_row', '');
|
|
const isSelected = getParamValue() === String(dataIndex);
|
|
|
|
const IMPACT_ICON_MAPPING: Record<keyof HydratedA11yFrame['impact'], ReactNode> = {
|
|
minor: <IconInfo size="xs" />,
|
|
moderate: <IconInfo size="xs" />,
|
|
serious: <IconWarning size="xs" color={isSelected ? 'white' : 'yellow400'} />,
|
|
critical: <IconFire size="xs" color={isSelected ? 'white' : 'red400'} />,
|
|
};
|
|
|
|
const hasOccurred = currentTime >= a11yIssue.offsetMs;
|
|
const isBeforeHover =
|
|
currentHoverTime === undefined || currentHoverTime >= a11yIssue.offsetMs;
|
|
|
|
const isByTimestamp = sortConfig.by === 'timestampMs';
|
|
const isAsc = isByTimestamp ? sortConfig.asc : undefined;
|
|
const columnProps = {
|
|
className: classNames({
|
|
beforeCurrentTime: isByTimestamp
|
|
? isAsc
|
|
? hasOccurred
|
|
: !hasOccurred
|
|
: undefined,
|
|
afterCurrentTime: isByTimestamp
|
|
? isAsc
|
|
? !hasOccurred
|
|
: hasOccurred
|
|
: undefined,
|
|
beforeHoverTime:
|
|
isByTimestamp && currentHoverTime !== undefined
|
|
? isAsc
|
|
? isBeforeHover
|
|
: !isBeforeHover
|
|
: undefined,
|
|
afterHoverTime:
|
|
isByTimestamp && currentHoverTime !== undefined
|
|
? isAsc
|
|
? !isBeforeHover
|
|
: isBeforeHover
|
|
: undefined,
|
|
}),
|
|
hasOccurred: isByTimestamp ? hasOccurred : undefined,
|
|
isSelected,
|
|
onClick: () => onClickCell({dataIndex, rowIndex}),
|
|
onMouseEnter: () => onMouseEnter(a11yIssue),
|
|
onMouseLeave: () => onMouseLeave(a11yIssue),
|
|
ref,
|
|
style,
|
|
} as ComponentProps<typeof Cell>;
|
|
|
|
const renderFns = [
|
|
() => (
|
|
<StyledCell {...columnProps} impact={a11yIssue.impact} isRowSelected={isSelected}>
|
|
<Text>
|
|
{a11yIssue.impact ? (
|
|
<Tooltip title={a11yIssue.impact ?? EMPTY_CELL}>
|
|
{IMPACT_ICON_MAPPING[a11yIssue.impact]}
|
|
</Tooltip>
|
|
) : (
|
|
EMPTY_CELL
|
|
)}
|
|
</Text>
|
|
</StyledCell>
|
|
),
|
|
() => (
|
|
<StyledCell {...columnProps} impact={a11yIssue.impact} isRowSelected={isSelected}>
|
|
<Text>{a11yIssue.id ?? EMPTY_CELL}</Text>
|
|
</StyledCell>
|
|
),
|
|
() => (
|
|
<StyledCell {...columnProps} impact={a11yIssue.impact} isRowSelected={isSelected}>
|
|
<Tooltip
|
|
title={a11yIssue.element.element ?? EMPTY_CELL}
|
|
isHoverable
|
|
showOnlyOnOverflow
|
|
overlayStyle={{maxWidth: '500px !important'}}
|
|
>
|
|
<StyledTextOverflow>
|
|
{a11yIssue.element.element ?? EMPTY_CELL}
|
|
</StyledTextOverflow>
|
|
</Tooltip>
|
|
</StyledCell>
|
|
),
|
|
];
|
|
|
|
return renderFns[columnIndex]();
|
|
}
|
|
);
|
|
|
|
export default AccessibilityTableCell;
|
|
|
|
const StyledTextOverflow = styled(TextOverflow)`
|
|
padding-right: ${space(1)};`;
|
|
|
|
const StyledCell = styled(Cell)<{
|
|
impact: HydratedA11yFrame['impact'];
|
|
isRowSelected: boolean;
|
|
}>`
|
|
background: ${p =>
|
|
p.isSelected
|
|
? p.theme.purple300
|
|
: p.impact === 'serious'
|
|
? p.theme.yellow100
|
|
: p.impact === 'critical'
|
|
? p.theme.red100
|
|
: 'transparent'}
|
|
`;
|