From a10ca1e0405f04a03633b3eeae4ee4054e8fe41e Mon Sep 17 00:00:00 2001 From: Jared Stoffan Date: Wed, 2 Dec 2020 15:59:32 -0800 Subject: [PATCH] feat(controls): Add react versions of mp3 controls --- src/lib/viewers/controls/_styles.scss | 33 +++-- .../annotations/AnnotationsButton.tsx | 4 +- src/lib/viewers/controls/hooks/index.ts | 3 +- .../viewers/controls/hooks/useAttention.ts | 30 +++++ .../viewers/controls/hooks/usePreventKey.ts | 26 ++++ .../controls/media/DurationLabels.scss | 17 +++ .../viewers/controls/media/DurationLabels.tsx | 32 +++++ .../viewers/controls/media/MediaToggle.tsx | 12 ++ .../controls/media/PlayPauseToggle.scss | 5 + .../controls/media/PlayPauseToggle.tsx | 42 +++++++ .../controls/media/SettingsControls.scss | 5 + .../controls/media/SettingsControls.tsx | 22 ++++ .../controls/media/VolumeControls.scss | 22 ++++ .../viewers/controls/media/VolumeControls.tsx | 119 ++++++++++++++++++ .../media/__tests__/VolumeControls-test.tsx | 14 +++ .../controls/slider/SliderControl.scss | 99 +++++++++++++++ .../viewers/controls/slider/SliderControl.tsx | 36 ++++++ src/lib/viewers/media/MP3Controls.scss | 14 +++ src/lib/viewers/media/MP3Controls.tsx | 32 +++++ src/lib/viewers/media/MP3Viewer.js | 36 +++++- src/lib/viewers/media/MediaBaseViewer.js | 77 ++++++++++-- src/lib/viewers/media/MediaControlsRoot.scss | 6 + src/lib/viewers/media/MediaControlsRoot.tsx | 33 +++++ .../media/__tests__/MP3Controls-test.jsx | 7 ++ 24 files changed, 698 insertions(+), 28 deletions(-) create mode 100644 src/lib/viewers/controls/hooks/useAttention.ts create mode 100644 src/lib/viewers/controls/hooks/usePreventKey.ts create mode 100644 src/lib/viewers/controls/media/DurationLabels.scss create mode 100644 src/lib/viewers/controls/media/DurationLabels.tsx create mode 100644 src/lib/viewers/controls/media/MediaToggle.tsx create mode 100644 src/lib/viewers/controls/media/PlayPauseToggle.scss create mode 100644 src/lib/viewers/controls/media/PlayPauseToggle.tsx create mode 100644 src/lib/viewers/controls/media/SettingsControls.scss create mode 100644 src/lib/viewers/controls/media/SettingsControls.tsx create mode 100644 src/lib/viewers/controls/media/VolumeControls.scss create mode 100644 src/lib/viewers/controls/media/VolumeControls.tsx create mode 100644 src/lib/viewers/controls/media/__tests__/VolumeControls-test.tsx create mode 100644 src/lib/viewers/controls/slider/SliderControl.scss create mode 100644 src/lib/viewers/controls/slider/SliderControl.tsx create mode 100644 src/lib/viewers/media/MP3Controls.scss create mode 100644 src/lib/viewers/media/MP3Controls.tsx create mode 100644 src/lib/viewers/media/MediaControlsRoot.scss create mode 100644 src/lib/viewers/media/MediaControlsRoot.tsx create mode 100644 src/lib/viewers/media/__tests__/MP3Controls-test.jsx diff --git a/src/lib/viewers/controls/_styles.scss b/src/lib/viewers/controls/_styles.scss index a1b836331e..f05bf75484 100644 --- a/src/lib/viewers/controls/_styles.scss +++ b/src/lib/viewers/controls/_styles.scss @@ -1,6 +1,27 @@ @import '~box-ui-elements/es/styles/variables'; +@mixin bp-Control--hover { + opacity: .7; + transition: opacity 150ms; + + &:focus, + &:hover { + opacity: 1; + } +} + +@mixin bp-Control--focus { + outline: 0; + + &:focus { + box-shadow: inset 0 0 0 1px fade-out($white, .5), 0 1px 2px fade-out($black, .9); + } +} + @mixin bp-ControlButton($height: 48px, $width: 48px) { + @include bp-Control--hover; + @include bp-Control--focus; + display: flex; align-items: center; justify-content: center; @@ -11,23 +32,11 @@ color: $white; background: transparent; border: 1px solid transparent; - outline: 0; cursor: pointer; - opacity: .7; - transition: opacity 150ms; user-select: none; touch-action: manipulation; zoom: 1; - &:focus, - &:hover { - opacity: 1; - } - - &:focus { - box-shadow: inset 0 0 0 1px fade-out($white, .5), 0 1px 2px fade-out($black, .9); - } - &:disabled { cursor: default; opacity: .2; diff --git a/src/lib/viewers/controls/annotations/AnnotationsButton.tsx b/src/lib/viewers/controls/annotations/AnnotationsButton.tsx index c2a3f20d27..67262924ff 100644 --- a/src/lib/viewers/controls/annotations/AnnotationsButton.tsx +++ b/src/lib/viewers/controls/annotations/AnnotationsButton.tsx @@ -1,10 +1,10 @@ -import React, { ButtonHTMLAttributes } from 'react'; +import React from 'react'; import classNames from 'classnames'; import noop from 'lodash/noop'; import { AnnotationMode } from './types'; import './AnnotationsButton.scss'; -export type Props = Omit, 'onClick'> & { +export type Props = Omit, 'onClick'> & { children?: React.ReactNode; className?: string; isActive?: boolean; diff --git a/src/lib/viewers/controls/hooks/index.ts b/src/lib/viewers/controls/hooks/index.ts index c9f0eddfea..8b27d74f0c 100644 --- a/src/lib/viewers/controls/hooks/index.ts +++ b/src/lib/viewers/controls/hooks/index.ts @@ -1,2 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export +export { default as useAttention } from './useAttention'; export { default as useFullscreen } from './useFullscreen'; +export { default as usePreventKey } from './usePreventKey'; diff --git a/src/lib/viewers/controls/hooks/useAttention.ts b/src/lib/viewers/controls/hooks/useAttention.ts new file mode 100644 index 0000000000..96e356d96f --- /dev/null +++ b/src/lib/viewers/controls/hooks/useAttention.ts @@ -0,0 +1,30 @@ +import * as React from 'react'; + +export type handlers = { + onBlur: () => void; + onFocus: () => void; + onMouseOut: () => void; + onMouseOver: () => void; +}; + +export type isActive = boolean; + +export default function useAttention(): [isActive, handlers] { + const [isFocused, setFocused] = React.useState(false); + const [isHovered, setHovered] = React.useState(false); + + const handleBlur = (): void => setFocused(false); + const handleFocus = (): void => setFocused(true); + const handleMouseOut = (): void => setHovered(false); + const handleMouseOver = (): void => setHovered(true); + + return [ + isFocused || isHovered, + { + onBlur: handleBlur, + onFocus: handleFocus, + onMouseOut: handleMouseOut, + onMouseOver: handleMouseOver, + }, + ]; +} diff --git a/src/lib/viewers/controls/hooks/usePreventKey.ts b/src/lib/viewers/controls/hooks/usePreventKey.ts new file mode 100644 index 0000000000..a8402d1beb --- /dev/null +++ b/src/lib/viewers/controls/hooks/usePreventKey.ts @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { decodeKeydown } from '../../../util'; + +export default function usePreventKey(ref: React.RefObject, keys = ['Enter', 'Space']): void { + React.useEffect(() => { + const { current: element } = ref; + + const handleKeydown = (event: KeyboardEvent): void => { + const key = decodeKeydown(event); + + if (keys.includes(key)) { + event.stopPropagation(); // Prevents global key handling. Can be simplified with React v17. + } + }; + + if (element) { + element.addEventListener('keydown', handleKeydown); + } + + return (): void => { + if (element) { + element.removeEventListener('keydown', handleKeydown); + } + }; + }, [keys, ref]); +} diff --git a/src/lib/viewers/controls/media/DurationLabels.scss b/src/lib/viewers/controls/media/DurationLabels.scss new file mode 100644 index 0000000000..999a8298fc --- /dev/null +++ b/src/lib/viewers/controls/media/DurationLabels.scss @@ -0,0 +1,17 @@ +@import '../styles'; + +.bp-DurationLabels { + display: flex; + align-items: center; + margin-right: 10px; + margin-left: 10px; +} + +.bp-DurationLabels-label { + @include bp-Control--hover; + + padding-right: 4px; + padding-left: 4px; + color: $white; + cursor: default; +} diff --git a/src/lib/viewers/controls/media/DurationLabels.tsx b/src/lib/viewers/controls/media/DurationLabels.tsx new file mode 100644 index 0000000000..bac3f422ba --- /dev/null +++ b/src/lib/viewers/controls/media/DurationLabels.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import './DurationLabels.scss'; + +export type Props = { + currentTime?: number; + durationTime?: number; +}; + +export function formatTime(time: number): string { + const hours = Math.floor(time / 3600); + const minutes = Math.floor((time % 3600) / 60); + const seconds = Math.floor((time % 3600) % 60); + const hour = hours > 0 ? `${hours.toString()}:` : ''; + const sec = seconds < 10 ? `0${seconds.toString()}` : seconds.toString(); + let min = minutes.toString(); + + if (hours > 0 && minutes < 10) { + min = `0${min}`; + } + + return `${hour}${min}:${sec}`; +} + +export default function DurationLabels({ currentTime = 0, durationTime = 0 }: Props): JSX.Element { + return ( +
+ {formatTime(currentTime)} + / + {formatTime(durationTime)} +
+ ); +} diff --git a/src/lib/viewers/controls/media/MediaToggle.tsx b/src/lib/viewers/controls/media/MediaToggle.tsx new file mode 100644 index 0000000000..c5325e557a --- /dev/null +++ b/src/lib/viewers/controls/media/MediaToggle.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import usePreventKey from '../hooks/usePreventKey'; + +export type Props = React.ButtonHTMLAttributes; + +export default function MediaToggle(props: Props): JSX.Element { + const buttonElRef = React.useRef(null); + + usePreventKey(buttonElRef, ['Enter', 'Space']); + + return