Skip to content
Open
139 changes: 139 additions & 0 deletions static/app/components/HoverScrollable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {useCallback, useRef, useState} from 'react';
import styled from '@emotion/styled';

import {Container} from '@sentry/scraps/layout';

interface HoverScrollableProps {
value: string;
className?: string;
leftTrim?: boolean;
maxLength?: number;
minLength?: number;
trimRegex?: RegExp;
}

export function HoverScrollable({
value,
trimRegex,
className,
minLength = 15,
maxLength = 50,
leftTrim = false,
}: HoverScrollableProps) {
const [isHovered, setIsHovered] = useState(false);
const [translateX, setTranslateX] = useState(0);

const containerRef = useRef<HTMLSpanElement>(null);
const contentRef = useRef<HTMLSpanElement>(null);

const isTruncated = value.length > maxLength;

const handleMouseMove = useCallback((e: React.MouseEvent<HTMLSpanElement>) => {
const container = containerRef.current;
const content = contentRef.current;

if (!container || !content) {
return;
}

const overflow = content.scrollWidth - container.clientWidth;

if (overflow <= 0) {
setTranslateX(0);
return;
}

const rect = container.getBoundingClientRect();

const progress = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));

setTranslateX(-overflow * progress);
}, []);

let shortValue: React.ReactNode;

if (isTruncated) {
const slicedValue = leftTrim
? value.slice(value.length - (maxLength - 4))
: value.slice(0, maxLength - 4);

if (trimRegex && leftTrim) {
const valueIndex = slicedValue.search(trimRegex);

shortValue = (
<span>
{' '}
{valueIndex > 0 && valueIndex <= maxLength - minLength
? slicedValue.slice(valueIndex)
: slicedValue}
</span>
);
} else if (trimRegex && !leftTrim) {
const matches = slicedValue.match(trimRegex);

let lastIndex = matches
? slicedValue.lastIndexOf(matches[matches.length - 1]!) + 1
: slicedValue.length;

if (lastIndex <= minLength) {
lastIndex = slicedValue.length;
}

shortValue = <span>{slicedValue.slice(0, lastIndex)}</span>;
} else if (leftTrim) {
shortValue = <span>{slicedValue}</span>;
} else {
shortValue = <span>{slicedValue}</span>;
}
} else {
shortValue = value;
}
Comment thread
kitlord marked this conversation as resolved.
Outdated

if (!isTruncated) {
return <span className={className}>{value}</span>;
}

return (
<Container
as="span"
className={className}
display="inline-block"
maxWidth="100%"
overflow="hidden"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setTranslateX(0);
}}
>
{isHovered ? (
<SlidingContainer ref={containerRef} onMouseMove={handleMouseMove}>
<SlidingText
ref={contentRef}
style={{
transform: `translateX(${translateX}px)`,
}}
>
{value}
</SlidingText>
</SlidingContainer>
) : (
shortValue
)}
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
Comment thread
cursor[bot] marked this conversation as resolved.
</Container>
);
}

const SlidingContainer = styled('span')`
display: inline-block;
width: 100%;
max-width: 100%;
overflow: hidden;
vertical-align: bottom;
`;

const SlidingText = styled('span')`
display: inline-block;
white-space: nowrap;
will-change: transform;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
trimPackage,
} from 'sentry/components/events/interfaces/frame/utils';
import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
import {HoverScrollable} from 'sentry/components/HoverScrollable';
import {QuestionTooltip} from 'sentry/components/questionTooltip';
import {Truncate} from 'sentry/components/truncate';
import {SLOW_TOOLTIP_DELAY} from 'sentry/constants';
Expand Down Expand Up @@ -139,7 +140,7 @@ export function DefaultTitle({
meta={pathNameOrModule.meta}
/>
) : (
<Truncate value={pathNameOrModule.value} maxLength={100} leftTrim />
<HoverScrollable value={pathNameOrModule.value} maxLength={100} leftTrim />
)}
</code>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,13 @@ const DefaultLineTitleWrapper = styled('div')<{isInAppFrame: boolean}>`
const LeftLineTitle = styled('div')`
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;

> div {
min-width: 0;
max-width: 100%;
}
`;

const RepeatedContent = styled(LeftLineTitle)`
Expand Down Expand Up @@ -418,6 +424,7 @@ const DefaultLine = styled('div')<{
min-height: 40px;
word-break: break-word;
padding: ${p => p.theme.space.sm} ${p => p.theme.space.lg};
gap: ${p => p.theme.space.sm} ${p => p.theme.space.lg};
font-size: ${p => p.theme.font.size.sm};
line-height: 16px;
cursor: ${p => (p.isExpandable ? 'pointer' : 'default')};
Expand Down
Loading