Browse Source

Tooltip fixes (#1054)

* button tooltip fixes

* create PlayerControlButton component

* cr fixes

* focus refactoring
Bartosz Dryl 3 years ago
parent
commit
d52a3ec24c

+ 117 - 0
src/shared/components/VideoPlayer/PlayerControlButton.style.ts

@@ -0,0 +1,117 @@
+import styled from '@emotion/styled'
+
+import { colors, sizes, transitions } from '@/shared/theme'
+
+import { Text } from '../Text'
+
+type ControlButtonProps = {
+  showTooltipOnlyOnFocus?: boolean
+  disableFocus?: boolean
+}
+
+export const ControlButton = styled.button<ControlButtonProps>`
+  margin-right: 0.5em;
+  display: flex !important;
+  padding: 0.5em;
+  cursor: pointer;
+  border: none;
+  background: none;
+  border-radius: 100%;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  transition: background ${transitions.timings.player} ease-in !important;
+
+  & > svg {
+    filter: drop-shadow(0 1px 2px ${colors.transparentBlack[32]});
+    width: 1.5em;
+    height: 1.5em;
+  }
+
+  :hover,
+  :focus,
+  :focus-visible {
+    /* turn off transition on mouse enter, but turn on on mouse leave */
+    transition: none !important;
+    ${() => ControlButtonTooltip} {
+      transition: none !important;
+    }
+  }
+
+  :hover {
+    background-color: ${colors.transparentPrimary[18]};
+    backdrop-filter: blur(${sizes(8)});
+
+    ${() => ControlButtonTooltip} {
+      opacity: ${({ showTooltipOnlyOnFocus }) => (showTooltipOnlyOnFocus ? 0 : 1)};
+    }
+  }
+
+  :active {
+    background-color: ${colors.transparentPrimary[10]};
+  }
+
+  :focus {
+    /* Provide a fallback style for browsers
+    that don't support :focus-visible e.g safari */
+    box-shadow: inset 0 0 0 3px ${colors.transparentPrimary[18]};
+    ${() => ControlButtonTooltip} {
+      opacity: ${({ disableFocus }) => (disableFocus ? 0 : 1)};
+    }
+  }
+
+  :focus-visible {
+    box-shadow: inset 0 0 0 3px ${colors.transparentPrimary[18]};
+    ${() => ControlButtonTooltip} {
+      opacity: ${({ disableFocus }) => (disableFocus ? 0 : 1)};
+    }
+  }
+
+  :focus:not(:focus-visible) {
+    box-shadow: unset;
+  }
+
+  :hover:focus {
+    ${() => ControlButtonTooltip} {
+      opacity: 1;
+    }
+  }
+
+  :focus:not(:focus-visible):hover {
+    ${() => ControlButtonTooltip} {
+      opacity: ${({ showTooltipOnlyOnFocus }) => (showTooltipOnlyOnFocus ? 0 : 1)};
+    }
+  }
+
+  :focus:not(:focus-visible):not(:hover) {
+    ${() => ControlButtonTooltip} {
+      opacity: 0;
+    }
+  }
+`
+
+type ControlButtonTooltipProps = {
+  tooltipPosition?: 'left' | 'right'
+}
+
+export const ControlButtonTooltip = styled.div<ControlButtonTooltipProps>`
+  ${({ tooltipPosition }) => tooltipPosition === 'left' && 'left: 0'};
+  ${({ tooltipPosition }) => tooltipPosition === 'right' && 'right: 0'};
+
+  opacity: 0;
+  pointer-events: none;
+  position: absolute;
+  bottom: 3em;
+  background: ${colors.transparentBlack[54]};
+  backdrop-filter: blur(${sizes(8)});
+  display: flex;
+  align-items: center;
+  padding: 0.5em;
+  white-space: nowrap;
+  transition: opacity ${transitions.timings.player} ease-in, backdrop-filter ${transitions.timings.player} ease-in !important;
+`
+
+export const ControlButtonTooltipText = styled(Text)`
+  /* 12px */
+  font-size: 0.75em;
+`

+ 47 - 0
src/shared/components/VideoPlayer/PlayerControlButton.tsx

@@ -0,0 +1,47 @@
+import React, { useEffect, useState } from 'react'
+
+import { ControlButton, ControlButtonTooltip, ControlButtonTooltipText } from './PlayerControlButton.style'
+
+type PlayerControlButtonProps = {
+  className?: string
+  showTooltipOnlyOnFocus?: boolean
+  tooltipPosition?: 'left' | 'right'
+  onClick?: (e: React.MouseEvent) => void
+  tooltipText?: string
+}
+
+export const PlayerControlButton: React.FC<PlayerControlButtonProps> = ({
+  children,
+  onClick,
+  tooltipText,
+  tooltipPosition,
+  className,
+  showTooltipOnlyOnFocus,
+}) => {
+  const [disableFocus, setDisableFocus] = useState(true)
+
+  useEffect(() => {
+    if (disableFocus) {
+      return
+    }
+    const handler = () => setDisableFocus(true)
+    window.addEventListener('mousemove', handler)
+    return () => window.removeEventListener('mousemove', handler)
+  }, [disableFocus])
+
+  const handleButtonFocus = () => setDisableFocus(false)
+  return (
+    <ControlButton
+      showTooltipOnlyOnFocus={showTooltipOnlyOnFocus}
+      className={className}
+      onFocus={handleButtonFocus}
+      disableFocus={disableFocus}
+      onClick={onClick}
+    >
+      {children}
+      <ControlButtonTooltip tooltipPosition={tooltipPosition}>
+        <ControlButtonTooltipText variant="caption">{tooltipText}</ControlButtonTooltipText>
+      </ControlButtonTooltip>
+    </ControlButton>
+  )
+}

+ 15 - 109
src/shared/components/VideoPlayer/VideoPlayer.style.tsx

@@ -3,6 +3,9 @@ import styled from '@emotion/styled'
 
 import { SvgPlayerSoundOff } from '@/shared/icons'
 
+import { PlayerControlButton } from './PlayerControlButton'
+import { ControlButton } from './PlayerControlButton.style'
+
 import { colors, sizes, transitions, zIndex } from '../../theme'
 import { Text } from '../Text'
 
@@ -15,30 +18,6 @@ type CustomControlsProps = {
   isEnded?: boolean
 }
 
-type ControlButtonProps = {
-  tooltipText?: string
-  tooltipPosition?: 'left' | 'right'
-  showTooltipOnlyOnFocus?: boolean
-}
-
-const focusStyles = css`
-  :focus {
-    /* Provide a fallback style for browsers
-     that don't support :focus-visible e.g safari */
-    box-shadow: inset 0 0 0 3px ${colors.transparentPrimary[18]};
-  }
-
-  /* https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
-
-  :focus-visible {
-    box-shadow: inset 0 0 0 3px ${colors.transparentPrimary[18]};
-  }
-
-  :focus:not(:focus-visible) {
-    box-shadow: unset;
-  }
-`
-
 export const ControlsOverlay = styled.div<CustomControlsProps>`
   font-size: ${({ isFullScreen }) => (isFullScreen ? sizes(8) : sizes(4))};
   opacity: 0;
@@ -64,89 +43,6 @@ export const CustomControls = styled.div<CustomControlsProps>`
   transition: transform 200ms ${transitions.easing}, opacity 200ms ${transitions.easing};
 `
 
-export const ControlButton = styled.button<ControlButtonProps>`
-  margin-right: 0.5em;
-  display: flex !important;
-  cursor: pointer;
-  border: none;
-  background: none;
-  border-radius: 100%;
-  align-items: center;
-  justify-content: center;
-  padding: 0.5em;
-  position: relative;
-  transition: background ${transitions.timings.player} ease-in !important;
-
-  & > svg {
-    filter: drop-shadow(0 1px 2px ${colors.transparentBlack[32]});
-    width: 1.5em;
-    height: 1.5em;
-  }
-
-  ::before {
-    ${({ tooltipPosition }) => tooltipPosition === 'left' && 'left: 0'};
-    ${({ tooltipPosition }) => tooltipPosition === 'right' && 'right: 0'};
-
-    opacity: 0;
-    pointer-events: none;
-    content: ${({ tooltipText }) => tooltipText && `'${tooltipText}'`};
-    position: absolute;
-    font-size: 0.875em;
-    bottom: calc(3.5em - 1px);
-    background: ${colors.transparentBlack[54]};
-    backdrop-filter: blur(${sizes(8)});
-    display: flex;
-    align-items: center;
-    padding: 0.5em;
-    white-space: nowrap;
-    transition: opacity ${transitions.timings.player} ease-in !important;
-  }
-
-  :hover {
-    background-color: ${colors.transparentPrimary[18]};
-    backdrop-filter: blur(${sizes(8)});
-    transition: none !important;
-
-    ::before {
-      transition: none !important;
-      opacity: ${({ showTooltipOnlyOnFocus }) => (showTooltipOnlyOnFocus ? 0 : 1)};
-    }
-  }
-
-  :active {
-    background-color: ${colors.transparentPrimary[10]};
-  }
-
-  :focus {
-    ::before {
-      /* turn off transition on mouse enter, but turn on on mouse leave */
-      transition: none !important;
-      opacity: 1;
-    }
-  }
-
-  :focus-visible {
-    ::before {
-      opacity: 1;
-    }
-  }
-
-  :focus:not(:focus-visible):hover {
-    ::before {
-      transition: none !important;
-      opacity: ${({ showTooltipOnlyOnFocus }) => (showTooltipOnlyOnFocus ? 0 : 1)};
-    }
-  }
-
-  :focus:not(:focus-visible):not(:hover) {
-    ::before {
-      opacity: 0;
-    }
-  }
-
-  ${focusStyles}
-`
-
 export const VolumeSliderContainer = styled.div`
   display: flex;
   align-items: center;
@@ -209,7 +105,7 @@ export const VolumeControl = styled.div`
     }
   }
 `
-export const VolumeButton = styled(ControlButton)`
+export const VolumeButton = styled(PlayerControlButton)`
   cursor: pointer;
   margin-right: 0;
   display: flex;
@@ -357,7 +253,17 @@ export const Container = styled.div<ContainerProps>`
         background-color: ${colors.transparentWhite[32]};
         transition: height ${transitions.timings.player} ${transitions.easing} !important;
 
-        ${focusStyles}
+        :focus {
+          box-shadow: inset 0 0 0 3px ${colors.transparentPrimary[18]};
+        }
+
+        :focus-visible {
+          box-shadow: inset 0 0 0 3px ${colors.transparentPrimary[18]};
+        }
+
+        :focus:not(:focus-visible) {
+          box-shadow: unset;
+        }
 
         .vjs-slider-bar {
           background-color: ${colors.blue[500]};

+ 9 - 10
src/shared/components/VideoPlayer/VideoPlayer.tsx

@@ -18,12 +18,12 @@ import { Logger } from '@/utils/logger'
 import { formatDurationShort } from '@/utils/time'
 
 import { ControlsIndicator } from './ControlsIndicator'
+import { PlayerControlButton } from './PlayerControlButton'
 import { VideoOverlay } from './VideoOverlay'
 import {
   BigPlayButton,
   BigPlayButtonOverlay,
   Container,
-  ControlButton,
   ControlsOverlay,
   CurrentTime,
   CurrentTimeWrapper,
@@ -403,7 +403,6 @@ const VideoPlayerComponent: React.ForwardRefRenderFunction<HTMLVideoElement, Vid
 
   const showBigPlayButton = playerState === null && !isInBackground
   const showPlayerControls = !isInBackground && isLoaded && playerState
-
   return (
     <Container isFullScreen={isFullScreen} className={className} isInBackground={isInBackground}>
       <div data-vjs-player>
@@ -427,15 +426,15 @@ const VideoPlayerComponent: React.ForwardRefRenderFunction<HTMLVideoElement, Vid
           <>
             <ControlsOverlay isFullScreen={isFullScreen}>
               <CustomControls isFullScreen={isFullScreen} isEnded={playerState === 'ended'}>
-                <ControlButton
+                <PlayerControlButton
                   onClick={handlePlayPause}
                   tooltipText={isPlaying ? 'Pause (k)' : 'Play (k)'}
                   tooltipPosition="left"
                 >
                   {playerState === 'ended' ? <SvgPlayerRestart /> : isPlaying ? <SvgPlayerPause /> : <SvgPlayerPlay />}
-                </ControlButton>
+                </PlayerControlButton>
                 <VolumeControl>
-                  <VolumeButton onClick={handleMute} showTooltipOnlyOnFocus tooltipText="Volume">
+                  <VolumeButton tooltipText="Volume" showTooltipOnlyOnFocus onClick={handleMute}>
                     {renderVolumeButton()}
                   </VolumeButton>
                   <VolumeSliderContainer>
@@ -456,17 +455,17 @@ const VideoPlayerComponent: React.ForwardRefRenderFunction<HTMLVideoElement, Vid
                 </CurrentTimeWrapper>
                 <ScreenControls>
                   {isPiPSupported && (
-                    <ControlButton onClick={handlePictureInPicture} tooltipText="Picture-in-picture">
+                    <PlayerControlButton onClick={handlePictureInPicture} tooltipText="Picture-in-picture">
                       {isPiPEnabled ? <SvgPlayerPipDisable /> : <SvgPlayerPip />}
-                    </ControlButton>
+                    </PlayerControlButton>
                   )}
-                  <ControlButton
-                    onClick={handleFullScreen}
+                  <PlayerControlButton
                     tooltipPosition="right"
                     tooltipText={isFullScreen ? 'Exit full screen (f)' : 'Full screen (f)'}
+                    onClick={handleFullScreen}
                   >
                     {isFullScreen ? <SvgPlayerSmallScreen /> : <SvgPlayerFullScreen />}
-                  </ControlButton>
+                  </PlayerControlButton>
                 </ScreenControls>
               </CustomControls>
             </ControlsOverlay>