Browse Source

feat(theme): Add tooltipUnderline (dotted underline) utility (#33746)

* feat(theme): Add underline utilities to theme object

* ref(ui): Use tooltip indicators (dotted underlines))

* ref(theme): Remove linkUnderline util

This should be added in a separate PR.

* fix(global-styles): Use tooltipUnderline, not tooltipIndicator

* fix(tooltip): Forward trigger element's class name

* ref(theme): Rename tooltipIndicator to tooltipUnderline

* ref(theme): Don't use css function from emotion for tooltipUnderline

* ref(tooltip): Use shorter syntax for style prop

Co-authored-by: Evan Purkhiser <evanpurkhiser@gmail.com>

Co-authored-by: Evan Purkhiser <evanpurkhiser@gmail.com>
Vu Luong 2 years ago
parent
commit
2cf6a827cc

+ 0 - 1
static/app/components/events/eventDataSection.tsx

@@ -167,7 +167,6 @@ const SectionHeader = styled('div')<{isCentered?: boolean}>`
   }
   & small > span {
     color: ${p => p.theme.textColor};
-    border-bottom: 1px dotted ${p => p.theme.border};
     font-weight: normal;
   }
 

+ 1 - 1
static/app/components/events/interfaces/crashHeader/crashTitle.tsx

@@ -34,7 +34,7 @@ const CrashTitle = ({
           {title}
         </GuideAnchor>
         {onChange && (
-          <Tooltip title={t('Toggle stack trace order')}>
+          <Tooltip showUnderline title={t('Toggle stack trace order')}>
             <small>
               (
               <span onClick={handleToggleOrder}>

+ 1 - 0
static/app/components/group/seenInfo.tsx

@@ -48,6 +48,7 @@ class SeenInfo extends React.Component<Props> {
     return (
       <HovercardWrapper>
         <StyledHovercard
+          showUnderline
           header={
             <div>
               <TimeSinceWrapper>

+ 12 - 2
static/app/components/hovercard.tsx

@@ -66,6 +66,11 @@ interface HovercardProps {
    * If set, is used INSTEAD OF the hover action to determine whether the hovercard is shown
    */
   show?: boolean;
+  /**
+   * Whether to add a dotted underline to the trigger element, to indicate the
+   * presence of a tooltip.
+   */
+  showUnderline?: boolean;
   /**
    * Color of the arrow tip border
    */
@@ -149,14 +154,15 @@ function Hovercard(props: HovercardProps): React.ReactElement {
     <Manager>
       <Reference>
         {({ref}) => (
-          <span
+          <Trigger
             ref={ref}
             aria-describedby={tooltipId}
             className={props.containerClassName}
+            showUnderline={props.showUnderline}
             {...hoverProps}
           >
             {props.children}
-          </span>
+          </Trigger>
         )}
       </Reference>
       {createPortal(
@@ -273,6 +279,10 @@ function getTipDirection(
   return (prefix || 'top') as 'top' | 'bottom' | 'left' | 'right';
 }
 
+const Trigger = styled('span')<{showUnderline?: boolean}>`
+  ${p => p.showUnderline && p.theme.tooltipUnderline};
+`;
+
 const HovercardContainer = styled('div')`
   /* Some hovercards overlap the toplevel header and sidebar, and we need to appear on top */
   z-index: ${p => p.theme.zIndex.hovercard};

+ 23 - 3
static/app/components/tooltip.tsx

@@ -9,7 +9,7 @@ import {
 } from 'react';
 import {createPortal} from 'react-dom';
 import {Manager, Popper, PopperArrowProps, PopperProps, Reference} from 'react-popper';
-import {SerializedStyles} from '@emotion/react';
+import {SerializedStyles, useTheme} from '@emotion/react';
 import styled from '@emotion/styled';
 import {AnimatePresence, motion, MotionProps, MotionStyle} from 'framer-motion';
 import * as PopperJS from 'popper.js';
@@ -96,6 +96,12 @@ export interface InternalTooltipProps {
    */
   showOnlyOnOverflow?: boolean;
 
+  /**
+   * Whether to add a dotted underline to the trigger element, to indicate the
+   * presence of a tooltip.
+   */
+  showUnderline?: boolean;
+
   /**
    * If child node supports ref forwarding, you can skip apply a wrapper
    */
@@ -137,6 +143,7 @@ export function DO_NOT_USE_TOOLTIP({
   forceVisible,
   isHoverable,
   popperStyle,
+  showUnderline,
   showOnlyOnOverflow,
   skipWrapper,
   title,
@@ -146,6 +153,7 @@ export function DO_NOT_USE_TOOLTIP({
 }: InternalTooltipProps) {
   const [visible, setVisible] = useState(false);
   const tooltipId = useMemo(() => domId('tooltip-'), []);
+  const theme = useTheme();
 
   // Delayed open and close time handles
   const delayOpenTimeoutRef = useRef<number | undefined>(undefined);
@@ -234,13 +242,25 @@ export function DO_NOT_USE_TOOLTIP({
       (skipWrapper || typeof triggerChildren.type === 'string')
     ) {
       // Basic DOM nodes can be cloned and have more props applied.
-      return cloneElement(triggerChildren, {...containerProps, ref: setRef});
+      return cloneElement(triggerChildren, {
+        ...containerProps,
+        style: {
+          ...triggerChildren.props.style,
+          ...(showUnderline && theme.tooltipUnderline),
+        },
+        ref: setRef,
+      });
     }
 
     containerProps.containerDisplayMode = containerDisplayMode;
 
     return (
-      <Container {...containerProps} className={className} ref={setRef}>
+      <Container
+        {...containerProps}
+        style={showUnderline ? theme.tooltipUnderline : undefined}
+        className={className}
+        ref={setRef}
+      >
         {triggerChildren}
       </Container>
     );

+ 1 - 1
static/app/styles/global.tsx

@@ -14,7 +14,7 @@ const styles = (theme: Theme, isDark: boolean) => css`
   }
 
   abbr {
-    border-bottom: 1px dotted ${theme.gray300};
+    ${theme.tooltipUnderline};
   }
 
   a {

+ 10 - 0
static/app/utils/theme.tsx

@@ -564,6 +564,14 @@ const generateButtonTheme = (colors: BaseColors, alias: Aliases) => ({
   },
 });
 
+const generateUtils = (colors: BaseColors) => ({
+  tooltipUnderline: {
+    textDecoration: `underline dotted ${colors.gray300}`,
+    textDecorationThickness: '0.75px',
+    textUnderlineOffset: '1.25px',
+  },
+});
+
 const iconSizes = {
   xs: '12px',
   sm: '16px',
@@ -812,6 +820,7 @@ export const lightTheme = {
     ...darkColors,
     ...darkAliases,
   },
+  ...generateUtils(lightColors),
   alert: generateAlertTheme(lightColors, lightAliases),
   badge: generateBadgeTheme(lightColors),
   button: generateButtonTheme(lightColors, lightAliases),
@@ -834,6 +843,7 @@ export const darkTheme: Theme = {
     ...lightColors,
     ...lightAliases,
   },
+  ...generateUtils(darkColors),
   alert: generateAlertTheme(darkColors, darkAliases),
   badge: generateBadgeTheme(darkColors),
   button: generateButtonTheme(darkColors, darkAliases),

+ 1 - 2
static/app/views/organizationGroupDetails/eventToolbar.tsx

@@ -138,7 +138,7 @@ class GroupEventToolbar extends Component<Props> {
             </ExternalLink>
           </LinkContainer>
         </Heading>
-        <Tooltip title={this.getDateTooltip()} disableForVisualTest>
+        <Tooltip title={this.getDateTooltip()} showUnderline disableForVisualTest>
           <StyledDateTime
             format={is24Hours ? 'MMM D, YYYY HH:mm:ss zz' : 'll LTS z'}
             date={getDynamicText({
@@ -198,7 +198,6 @@ const StyledIconWarning = styled(IconWarning)`
 `;
 
 const StyledDateTime = styled(DateTime)`
-  border-bottom: 1px dotted #dfe3ea;
   color: ${p => p.theme.subText};
 `;
 

+ 1 - 0
static/app/views/organizationGroupDetails/header.tsx

@@ -198,6 +198,7 @@ class GroupHeader extends React.Component<Props, State> {
                       <h6 className="nav-header">
                         <Tooltip
                           className="help-link"
+                          showUnderline
                           title={t(
                             'This identifier is unique across your organization, and can be used to reference an issue in various places, like commit messages.'
                           )}

+ 0 - 1
static/less/shared-components.less

@@ -1074,7 +1074,6 @@ header + .alert {
   a.help-link,
   span.help-link a {
     color: inherit;
-    border-bottom: 1px dotted @gray-light;
   }
 
   .view-more {