Browse Source

ref(issue-details): Small changes all over (#79774)

Bunch of small changes!

## Search
- Prevent grid blowout on large inputs
<img width="1205" alt="image"
src="https://github.com/user-attachments/assets/4bcec6d6-153a-4b67-93cb-27849f068c3c">
<img width="1177" alt="image"
src="https://github.com/user-attachments/assets/180e4b92-3dd8-4a57-a270-bf6a578975ea">




## Activity Section
- The collapse button height was adjusted
- Longer titles (emails) get overflow to ellipsis with a tooltip
- Align the vertical ellipsis

<img width="352" alt="image"
src="https://github.com/user-attachments/assets/388293a8-67bf-4579-9114-5aab67c30d14">
<img width="329" alt="image"
src="https://github.com/user-attachments/assets/0298bca3-0e05-45ca-8caa-0270862d51ef">


## Screenshot Section
- Moves the entire section above the breadcrumbs, below mobile replay

<img width="754" alt="image"
src="https://github.com/user-attachments/assets/fa5e3314-1120-4a68-ae02-7c4ef6dceac6">


## General
- Increases font size for section header
- Increases padding between sections
- Status/Substatuses are back
- Auto-collapse SDK section
- Improve ARIA accessibility of folding sections

<img width="348" alt="image"
src="https://github.com/user-attachments/assets/ca2dfa35-018c-4143-833b-b7c83deae48e">


_Before_
<img width="928" alt="image"
src="https://github.com/user-attachments/assets/9f6e3a21-c18a-493c-8a5f-a3eae2079bb7">

_After_
<img width="863" alt="image"
src="https://github.com/user-attachments/assets/882682a4-61c4-4e98-913f-168bb0a7dfd4">
Leander Rodrigues 4 months ago
parent
commit
e44ad64a98

+ 1 - 1
static/app/components/events/eventSdk.tsx

@@ -18,7 +18,7 @@ export function EventSdk({sdk, meta}: Props) {
   }
 
   return (
-    <InterimSection title={t('SDK')} type={SectionKey.SDK}>
+    <InterimSection title={t('SDK')} type={SectionKey.SDK} initialCollapse>
       <KeyValueList
         data={[
           {

+ 1 - 1
static/app/components/events/highlights/highlightsIconSummary.tsx

@@ -67,7 +67,7 @@ export function HighlightsIconSummary({event}: HighlightsIconSummaryProps) {
           ))}
         </ScrollCarousel>
       </IconBar>
-      <SectionDivider />
+      <SectionDivider style={{marginTop: space(1)}} />
     </Fragment>
   ) : null;
 }

+ 3 - 3
static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx

@@ -283,6 +283,9 @@ export function EventDetailsContent({
           />
         </EntryErrorBoundary>
       )}
+      {hasStreamlinedUI && (
+        <ScreenshotDataSection event={event} projectSlug={project.slug} />
+      )}
       {isANR && (
         <QuickTraceQuery
           event={event}
@@ -400,9 +403,6 @@ export function EventDetailsContent({
       <EventPackageData event={event} />
       <EventDevice event={event} />
       <EventViewHierarchy event={event} project={project} />
-      {hasStreamlinedUI && (
-        <ScreenshotDataSection event={event} projectSlug={project.slug} />
-      )}
       <EventAttachments event={event} project={project} group={group} />
       <EventSdk sdk={event.sdk} meta={event._meta?.sdk} />
       {hasStreamlinedUI && (

+ 31 - 42
static/app/views/issueDetails/streamline/activitySection.tsx

@@ -5,9 +5,11 @@ import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicato
 import {NoteBody} from 'sentry/components/activity/note/body';
 import {NoteInputWithStorage} from 'sentry/components/activity/note/inputWithStorage';
 import {Button} from 'sentry/components/button';
+import {Flex} from 'sentry/components/container/flex';
 import useMutateActivity from 'sentry/components/feedback/useMutateActivity';
 import Timeline from 'sentry/components/timeline';
 import TimeSince from 'sentry/components/timeSince';
+import {Tooltip} from 'sentry/components/tooltip';
 import {IconEllipsis} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import GroupStore from 'sentry/stores/groupStore';
@@ -43,7 +45,7 @@ function TimelineItem({
     item,
     organization,
     group.project.id,
-    <Author>{authorName}</Author>,
+    <strong>{authorName}</strong>,
     teams
   );
 
@@ -52,16 +54,16 @@ function TimelineItem({
   return (
     <ActivityTimelineItem
       title={
-        <TitleWrapper>
-          {title}
-          <NoteDropdownWrapper>
-            {item.type === GroupActivityType.NOTE && (
-              <NoteDropdown onDelete={() => handleDelete(item)} user={item.user} />
-            )}
-          </NoteDropdownWrapper>
-        </TitleWrapper>
+        <Flex gap={space(0.5)} align="center" justify="flex-start">
+          <TitleTooltip title={title} showOnlyOnOverflow skipWrapper>
+            {title}
+          </TitleTooltip>
+          {item.type === GroupActivityType.NOTE && (
+            <TitleDropdown onDelete={() => handleDelete(item)} user={item.user} />
+          )}
+        </Flex>
       }
-      timestamp={<SmallTimestamp date={item.dateCreated} />}
+      timestamp={<Timestamp date={item.dateCreated} tooltipProps={{skipWrapper: true}} />}
       icon={
         Icon && (
           <Icon {...groupActivityTypeIconMapping[item.type].defaultProps} size="xs" />
@@ -136,14 +138,14 @@ export default function StreamlinedActivitySection({group}: {group: Group}) {
 
   return (
     <div>
-      <TitleSection>
+      <Flex justify="space-between" align="center">
         <SidebarSectionTitle>{t('Activity')}</SidebarSectionTitle>
         {showAll && (
-          <CollapseButton borderless size="zero" onClick={() => setShowAll(false)}>
+          <TextButton borderless size="zero" onClick={() => setShowAll(false)}>
             {t('Collapse')}
-          </CollapseButton>
+          </TextButton>
         )}
-      </TitleSection>
+      </Flex>
       <Timeline.Container>
         <NoteInputWithStorage
           key={inputId}
@@ -183,16 +185,16 @@ export default function StreamlinedActivitySection({group}: {group: Group}) {
             })}
             <ActivityTimelineItem
               title={
-                <ShowAllButton
+                <TextButton
                   aria-label={t('Show all activity')}
                   onClick={() => setShowAll(true)}
                   borderless
                   size="zero"
                 >
                   {t('%s activities hidden', group.activity.length - 3)}
-                </ShowAllButton>
+                </TextButton>
               }
-              icon={<RotatedEllipsisIcon />}
+              icon={<RotatedEllipsisIcon direction={'up'} />}
             />
             <TimelineItem
               item={group.activity[group.activity.length - 1]}
@@ -208,46 +210,33 @@ export default function StreamlinedActivitySection({group}: {group: Group}) {
   );
 }
 
-const Author = styled('span')`
-  font-weight: ${p => p.theme.fontWeightBold};
+const TitleTooltip = styled(Tooltip)`
+  justify-self: start;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 `;
 
-const NoteDropdownWrapper = styled('span')`
+const TitleDropdown = styled(NoteDropdown)`
   font-weight: normal;
 `;
 
-const TitleWrapper = styled('div')`
-  display: flex;
-  align-items: center;
-  gap: ${space(0.5)};
-`;
-
 const ActivityTimelineItem = styled(Timeline.Item)`
   align-items: center;
+  grid-template-columns: 22px minmax(50px, 1fr) auto;
 `;
 
-const SmallTimestamp = styled(TimeSince)`
-  font-size: ${p => p.theme.fontSizeSmall};
-`;
-
-const ShowAllButton = styled(Button)`
+const Timestamp = styled(TimeSince)`
   font-size: ${p => p.theme.fontSizeSmall};
-  color: ${p => p.theme.subText};
-  font-weight: ${p => p.theme.fontWeightNormal};
-`;
-
-const TitleSection = styled('div')`
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
+  white-space: nowrap;
 `;
 
-const CollapseButton = styled(Button)`
+const TextButton = styled(Button)`
   font-weight: ${p => p.theme.fontWeightNormal};
-  color: ${p => p.theme.subText};
   font-size: ${p => p.theme.fontSizeSmall};
+  color: ${p => p.theme.subText};
 `;
 
 const RotatedEllipsisIcon = styled(IconEllipsis)`
-  transform: rotate(90deg);
+  transform: rotate(90deg) translateY(1px);
 `;

+ 2 - 2
static/app/views/issueDetails/streamline/eventDetailsHeader.tsx

@@ -68,8 +68,8 @@ export function EventDetailsHeader({
 const FilterContainer = styled('div')`
   padding-left: 24px;
   display: grid;
-  grid-template-columns: auto auto 1fr;
-  grid-template-rows: 38px auto;
+  grid-template-columns: auto auto minmax(100px, 1fr);
+  grid-template-rows: minmax(38px, auto) auto;
   grid-template-areas:
     'env    date  searchFilter'
     'graph  graph graph';

+ 11 - 3
static/app/views/issueDetails/streamline/foldSection.tsx

@@ -12,6 +12,7 @@ import styled from '@emotion/styled';
 import ErrorBoundary from 'sentry/components/errorBoundary';
 import InteractionStateLayer from 'sentry/components/interactionStateLayer';
 import {IconChevron} from 'sentry/icons';
+import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import mergeRefs from 'sentry/utils/mergeRefs';
@@ -114,6 +115,8 @@ export const FoldSection = forwardRef<HTMLElement, FoldSectionProps>(function Fo
     },
     [organization, sectionKey, isCollapsed, setIsCollapsed]
   );
+  const labelPrefix = isCollapsed ? t('View') : t('Collapse');
+  const labelSuffix = typeof title === 'string' ? title + t(' Section') : t('Section');
 
   return (
     <Fragment>
@@ -125,8 +128,12 @@ export const FoldSection = forwardRef<HTMLElement, FoldSectionProps>(function Fo
         <SectionExpander
           preventCollapse={preventCollapse}
           onClick={preventCollapse ? e => e.preventDefault() : toggleCollapse}
+          role="button"
+          aria-label={`${labelPrefix} ${labelSuffix}`}
+          aria-expanded={!isCollapsed}
         >
           <InteractionStateLayer
+            hasSelectedBackground={false}
             hidden={preventCollapse ? preventCollapse : !isLayerEnabled}
           />
           <TitleWithActions>
@@ -158,7 +165,7 @@ export const FoldSection = forwardRef<HTMLElement, FoldSectionProps>(function Fo
 
 export const SectionDivider = styled('hr')`
   border-color: ${p => p.theme.translucentBorder};
-  margin: ${space(1)} 0;
+  margin: ${space(1.5)} 0;
   &:last-child {
     display: none;
   }
@@ -176,15 +183,16 @@ const SectionExpander = styled('div')<{preventCollapse: boolean}>`
   display: grid;
   grid-template-columns: 1fr auto;
   align-items: center;
-  padding: ${space(0.5)} ${space(0.75)};
+  padding: ${space(1)} ${space(0.75)};
   border-radius: ${p => p.theme.borderRadius};
   cursor: ${p => (p.preventCollapse ? 'initial' : 'pointer')};
   position: relative;
 `;
 
 const TitleWrapper = styled('div')`
-  font-size: ${p => p.theme.fontSizeMedium};
+  font-size: ${p => p.theme.fontSizeLarge};
   font-weight: ${p => p.theme.fontWeightBold};
+  user-select: none;
 `;
 
 const IconWrapper = styled('div')<{preventCollapse: boolean}>`

+ 17 - 2
static/app/views/issueDetails/streamline/header.tsx

@@ -7,6 +7,7 @@ import {Button} from 'sentry/components/button';
 import {Flex} from 'sentry/components/container/flex';
 import Count from 'sentry/components/count';
 import ErrorLevel from 'sentry/components/events/errorLevel';
+import {getBadgeProperties} from 'sentry/components/group/inboxBadges/statusBadge';
 import UnhandledTag from 'sentry/components/group/inboxBadges/unhandledTag';
 import Link from 'sentry/components/links/link';
 import {Tooltip} from 'sentry/components/tooltip';
@@ -62,6 +63,8 @@ export default function StreamlinedGroupHeader({
     true
   );
 
+  const statusProps = getBadgeProperties(group.status, group.substatus);
+
   return (
     <Fragment>
       <Header>
@@ -115,14 +118,23 @@ export default function StreamlinedGroupHeader({
           <Flex gap={space(1)} align="center" justify="flex-start">
             <ErrorLevel level={group.level} size={'10px'} />
             {group.isUnhandled && <UnhandledTag />}
+            {statusProps?.status ? (
+              <Fragment>
+                <Divider />
+                <Tooltip title={statusProps?.tooltip}>
+                  <Subtext>{statusProps?.status}</Subtext>
+                </Tooltip>
+              </Fragment>
+            ) : null}
             {subtitle && (
               <Fragment>
                 <Divider />
                 <Subtitle title={subtitle} isHoverable showOnlyOnOverflow delay={1000}>
-                  {subtitle}
+                  <Subtext>{subtitle}</Subtext>
                 </Subtitle>
               </Fragment>
             )}
+
             <AttachmentsBadge group={group} />
             <UserFeedbackBadge group={group} project={project} />
             <ReplayBadge group={group} project={project} />
@@ -213,11 +225,14 @@ const StatCount = styled(Count)`
   text-align: right;
 `;
 
+const Subtext = styled('span')`
+  color: ${p => p.theme.subText};
+`;
+
 const Subtitle = styled(Tooltip)`
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  color: ${p => p.theme.subText};
 `;
 
 const ActionBar = styled('div')<{isComplete: boolean}>`

+ 3 - 2
static/app/views/issueDetails/streamline/noteDropdown.tsx

@@ -1,5 +1,5 @@
 import {openConfirmModal} from 'sentry/components/confirm';
-import {DropdownMenu} from 'sentry/components/dropdownMenu';
+import {DropdownMenu, type DropdownMenuProps} from 'sentry/components/dropdownMenu';
 import {IconEllipsis} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import type {User} from 'sentry/types/user';
@@ -10,7 +10,7 @@ type Props = {
   user?: User | null;
 };
 
-function NoteDropdown({user, onDelete}: Props) {
+function NoteDropdown({user, onDelete, ...props}: Props & Partial<DropdownMenuProps>) {
   const activeUser = useUser();
   const canEdit = activeUser && (activeUser.isSuperuser || user?.id === activeUser.id);
 
@@ -47,6 +47,7 @@ function NoteDropdown({user, onDelete}: Props) {
             : undefined,
         },
       ]}
+      {...props}
     />
   );
 }