Browse Source

ref(issueDetails): Use `SegmentedControl` (#44080)

In Issue Details, use `SegmentedControl` rather than `ButtonBar`. See
screenshots below.

https://github.com/getsentry/sentry/issues/43865
Vu Luong 2 years ago
parent
commit
320ce2cb1a

+ 11 - 9
static/app/components/events/eventExtraData/index.tsx

@@ -1,9 +1,8 @@
 import {memo, useState} from 'react';
 
-import {Button} from 'sentry/components/button';
-import ButtonBar from 'sentry/components/buttonBar';
 import ContextBlock from 'sentry/components/events/contexts/contextBlock';
 import {EventDataSection} from 'sentry/components/events/eventDataSection';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {t} from 'sentry/locale';
 import {Event} from 'sentry/types/event';
 import {defined} from 'sentry/utils';
@@ -25,14 +24,17 @@ export const EventExtraData = memo(
         type="extra"
         title={t('Additional Data')}
         actions={
-          <ButtonBar merged active={raw ? 'raw' : 'formatted'}>
-            <Button barId="formatted" size="xs" onClick={() => setRaw(false)}>
+          <SegmentedControl
+            aria-label={t('View')}
+            size="xs"
+            value={raw ? 'raw' : 'formatted'}
+            onChange={key => setRaw(key === 'raw')}
+          >
+            <SegmentedControl.Item key="formatted">
               {t('Formatted')}
-            </Button>
-            <Button barId="raw" size="xs" onClick={() => setRaw(true)}>
-              {t('Raw')}
-            </Button>
-          </ButtonBar>
+            </SegmentedControl.Item>
+            <SegmentedControl.Item key="raw">{t('Raw')}</SegmentedControl.Item>
+          </SegmentedControl>
         }
       >
         {!defined(event.context) ? null : (

+ 54 - 76
static/app/components/events/interfaces/crashHeader/crashActions.tsx

@@ -1,7 +1,6 @@
 import styled from '@emotion/styled';
 
-import {Button} from 'sentry/components/button';
-import ButtonBar from 'sentry/components/buttonBar';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {
@@ -46,78 +45,62 @@ const CrashActions = ({
     ? false
     : !!exception?.values?.find(value => value.rawStacktrace) || !!thread?.rawStacktrace;
 
-  const notify = (options: NotifyOptions) => {
-    if (onChange) {
-      onChange(options);
-    }
+  const setStackType = (type: STACK_TYPE) => {
+    onChange?.({stackType: type});
   };
 
-  const setStackType = (type: STACK_TYPE) => () => {
-    notify({stackType: type});
+  const setStackView = (view: STACK_VIEW) => {
+    onChange?.({stackView: view});
   };
-
-  const setStackView = (view: STACK_VIEW) => () => {
-    notify({stackView: view});
-  };
-
-  const getOriginalButtonLabel = () => {
-    if (platform === 'javascript' || platform === 'node') {
-      return t('Original');
-    }
-
-    return t('Symbolicated');
-  };
-
-  const getMinifiedButtonLabel = () => {
-    if (platform === 'javascript' || platform === 'node') {
-      return t('Minified');
-    }
-    return t('Unsymbolicated');
-  };
-
   return (
     <ButtonGroupWrapper>
-      <ButtonBar active={stackView} merged>
-        {hasSystemFrames && (
-          <Button
-            barId={STACK_VIEW.APP}
-            size="xs"
-            onClick={setStackView(STACK_VIEW.APP)}
-            title={
-              hasHierarchicalGrouping
-                ? t(
-                    'The stack trace only shows application frames and frames responsible for grouping this issue'
-                  )
-                : undefined
-            }
-          >
-            {hasHierarchicalGrouping ? t('Most Relevant') : t('App Only')}
-          </Button>
-        )}
-        <Button barId={STACK_VIEW.FULL} size="xs" onClick={setStackView(STACK_VIEW.FULL)}>
-          {t('Full')}
-        </Button>
-        <Button barId={STACK_VIEW.RAW} onClick={setStackView(STACK_VIEW.RAW)} size="xs">
-          {t('Raw')}
-        </Button>
-      </ButtonBar>
+      <SegmentedControl
+        aria-label={t('View')}
+        size="xs"
+        value={stackView}
+        onChange={setStackView}
+      >
+        {[
+          ...(hasSystemFrames
+            ? [
+                <SegmentedControl.Item
+                  key={STACK_VIEW.APP}
+                  tooltip={
+                    hasHierarchicalGrouping &&
+                    t(
+                      'The stack trace only shows application frames and frames responsible for grouping this issue'
+                    )
+                  }
+                >
+                  {hasHierarchicalGrouping ? t('Most Relevant') : t('App Only')}
+                </SegmentedControl.Item>,
+              ]
+            : []),
+          <SegmentedControl.Item key={STACK_VIEW.FULL}>
+            {t('Full')}
+          </SegmentedControl.Item>,
+          <SegmentedControl.Item key={STACK_VIEW.RAW}>{t('Raw')}</SegmentedControl.Item>,
+        ]}
+      </SegmentedControl>
+
       {hasMinified && (
-        <ButtonBar active={stackType} merged>
-          <Button
-            barId={STACK_TYPE.ORIGINAL}
-            size="xs"
-            onClick={setStackType(STACK_TYPE.ORIGINAL)}
-          >
-            {getOriginalButtonLabel()}
-          </Button>
-          <Button
-            barId={STACK_TYPE.MINIFIED}
-            size="xs"
-            onClick={setStackType(STACK_TYPE.MINIFIED)}
-          >
-            {getMinifiedButtonLabel()}
-          </Button>
-        </ButtonBar>
+        <SegmentedControl
+          aria-label={t('Type')}
+          size="xs"
+          value={stackType}
+          onChange={setStackType}
+        >
+          <SegmentedControl.Item key={STACK_TYPE.ORIGINAL}>
+            {platform === 'javascript' || platform === 'node'
+              ? t('Original')
+              : t('Symbolicated')}
+          </SegmentedControl.Item>
+          <SegmentedControl.Item key={STACK_TYPE.MINIFIED}>
+            {platform === 'javascript' || platform === 'node'
+              ? t('Minified')
+              : t('Unsymbolicated')}
+          </SegmentedControl.Item>
+        </SegmentedControl>
       )}
     </ButtonGroupWrapper>
   );
@@ -126,12 +109,7 @@ const CrashActions = ({
 export default CrashActions;
 
 const ButtonGroupWrapper = styled('div')`
-  display: flex;
-  flex-wrap: wrap;
-  > * {
-    padding: ${space(0.5)} 0;
-  }
-  > *:not(:last-child) {
-    margin-right: ${space(1)};
-  }
+  display: grid;
+  grid-auto-flow: column;
+  gap: ${space(1)};
 `;

+ 6 - 13
static/app/components/events/interfaces/csp/index.tsx

@@ -1,9 +1,8 @@
 import {useState} from 'react';
 
-import {Button} from 'sentry/components/button';
-import ButtonBar from 'sentry/components/buttonBar';
 import {EventDataSection} from 'sentry/components/events/eventDataSection';
 import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {t} from 'sentry/locale';
 import {EntryType, Event} from 'sentry/types/event';
 
@@ -57,17 +56,11 @@ export function Csp({data, event}: Props) {
         };
 
   const actions = (
-    <ButtonBar merged active={view}>
-      <Button barId="report" size="xs" onClick={() => setView('report')}>
-        {t('Report')}
-      </Button>
-      <Button barId="raw" size="xs" onClick={() => setView('raw')}>
-        {t('Raw')}
-      </Button>
-      <Button barId="help" size="xs" onClick={() => setView('help')}>
-        {t('Help')}
-      </Button>
-    </ButtonBar>
+    <SegmentedControl aria-label={t('View')} size="xs" value={view} onChange={setView}>
+      <SegmentedControl.Item key="report">{t('Report')}</SegmentedControl.Item>
+      <SegmentedControl.Item key="raw">{t('Raw')}</SegmentedControl.Item>
+      <SegmentedControl.Item key="help">{t('Help')}</SegmentedControl.Item>
+    </SegmentedControl>
   );
 
   return (

+ 10 - 10
static/app/components/events/interfaces/generic.tsx

@@ -1,10 +1,9 @@
 import {useState} from 'react';
 
-import {Button} from 'sentry/components/button';
-import ButtonBar from 'sentry/components/buttonBar';
 import {EventDataSection} from 'sentry/components/events/eventDataSection';
 import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
 import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {t} from 'sentry/locale';
 
 function getView({
@@ -53,14 +52,15 @@ export function Generic({type, data, meta}: Props) {
       type={type}
       title={t('Report')}
       actions={
-        <ButtonBar merged active={view}>
-          <Button barId="report" size="xs" onClick={() => setView('report')}>
-            {t('Report')}
-          </Button>
-          <Button barId="raw" size="xs" onClick={() => setView('raw')}>
-            {t('Raw')}
-          </Button>
-        </ButtonBar>
+        <SegmentedControl
+          aria-label={t('View')}
+          size="xs"
+          value={view}
+          onChange={setView}
+        >
+          <SegmentedControl.Item key="report">{t('Report')}</SegmentedControl.Item>
+          <SegmentedControl.Item key="raw">{t('Raw')}</SegmentedControl.Item>
+        </SegmentedControl>
       }
     >
       {getView({view, data, meta})}

+ 9 - 10
static/app/components/events/interfaces/request/index.tsx

@@ -1,13 +1,12 @@
 import {Fragment, useState} from 'react';
 import styled from '@emotion/styled';
 
-import {Button} from 'sentry/components/button';
-import ButtonBar from 'sentry/components/buttonBar';
 import ClippedBox from 'sentry/components/clippedBox';
 import ErrorBoundary from 'sentry/components/errorBoundary';
 import {EventDataSection} from 'sentry/components/events/eventDataSection';
 import {getCurlCommand, getFullUrl} from 'sentry/components/events/interfaces/utils';
 import ExternalLink from 'sentry/components/links/externalLink';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import Truncate from 'sentry/components/truncate';
 import {IconOpen} from 'sentry/icons';
 import {t} from 'sentry/locale';
@@ -56,15 +55,15 @@ export function Request({data, event}: Props) {
 
   if (!isPartial && fullUrl) {
     actions = (
-      <ButtonBar merged active={view}>
-        <Button barId="formatted" size="xs" onClick={() => setView('formatted')}>
+      <SegmentedControl aria-label={t('View')} size="xs" value={view} onChange={setView}>
+        <SegmentedControl.Item key="formatted">
           {/* Translators: this means "formatted" rendering (fancy tables) */}
           {t('Formatted')}
-        </Button>
-        <MonoButton barId="curl" size="xs" onClick={() => setView('curl')}>
-          curl
-        </MonoButton>
-      </ButtonBar>
+        </SegmentedControl.Item>
+        <SegmentedControl.Item key="curl" textValue="curl">
+          <Monospace>curl</Monospace>
+        </SegmentedControl.Item>
+      </SegmentedControl>
     );
   }
 
@@ -143,7 +142,7 @@ export function Request({data, event}: Props) {
   );
 }
 
-const MonoButton = styled(Button)`
+const Monospace = styled('span')`
   font-family: ${p => p.theme.text.familyMono};
 `;
 

+ 6 - 1
static/app/components/segmentedControl.tsx

@@ -133,7 +133,12 @@ function Segment<Value extends string>({
 
   const {isDisabled} = props;
   const content = (
-    <SegmentWrap size={size} isSelected={isSelected} isDisabled={isDisabled}>
+    <SegmentWrap
+      size={size}
+      isSelected={isSelected}
+      isDisabled={isDisabled}
+      data-test-id={props.value}
+    >
       <SegmentInput {...inputProps} ref={ref} />
       {!isDisabled && (
         <SegmentInteractionStateLayer

+ 1 - 1
tests/acceptance/test_issue_details.py

@@ -118,7 +118,7 @@ class IssueDetailsTest(AcceptanceTestCase, SnubaTestCase):
         self.page.visit_issue(self.org.slug, event.group.id)
         self.browser.snapshot("issue details javascript - event details", desktop_only=True)
 
-        self.browser.click('[aria-label="curl"]')
+        self.browser.click('label[data-test-id="curl"]')
         self.browser.snapshot(
             "issue details javascript - event details - curl command", desktop_only=True
         )