Skip to content

Commit

Permalink
feat(controls): Add react versions of mp3 controls
Browse files Browse the repository at this point in the history
  • Loading branch information
jstoffan committed Dec 3, 2020
1 parent 3fc73a8 commit 4e9fd42
Show file tree
Hide file tree
Showing 24 changed files with 701 additions and 28 deletions.
33 changes: 21 additions & 12 deletions src/lib/viewers/controls/_styles.scss
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/viewers/controls/annotations/AnnotationsButton.tsx
Original file line number Diff line number Diff line change
@@ -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<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
export type Props = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
children?: React.ReactNode;
className?: string;
isActive?: boolean;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/viewers/controls/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -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';
30 changes: 30 additions & 0 deletions src/lib/viewers/controls/hooks/useAttention.ts
Original file line number Diff line number Diff line change
@@ -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,
},
];
}
26 changes: 26 additions & 0 deletions src/lib/viewers/controls/hooks/usePreventKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';
import { decodeKeydown } from '../../../util';

export default function usePreventKey(ref: React.RefObject<HTMLElement | null>, 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]);
}
17 changes: 17 additions & 0 deletions src/lib/viewers/controls/media/DurationLabels.scss
Original file line number Diff line number Diff line change
@@ -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;
}
32 changes: 32 additions & 0 deletions src/lib/viewers/controls/media/DurationLabels.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="bp-DurationLabels">
<span className="bp-DurationLabels-label">{formatTime(currentTime)}</span>
<span className="bp-DurationLabels-label">/</span>
<span className="bp-DurationLabels-label">{formatTime(durationTime)}</span>
</div>
);
}
12 changes: 12 additions & 0 deletions src/lib/viewers/controls/media/MediaToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import usePreventKey from '../hooks/usePreventKey';

export type Props = React.ButtonHTMLAttributes<HTMLButtonElement>;

export default function MediaToggle(props: Props): JSX.Element {
const buttonElRef = React.useRef<HTMLButtonElement>(null);

usePreventKey(buttonElRef, ['Enter', 'Space']);

return <button ref={buttonElRef} type="button" {...props} />;
}
5 changes: 5 additions & 0 deletions src/lib/viewers/controls/media/PlayPauseToggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '../styles';

.bp-PlayPauseToggle {
@include bp-ControlButton($height: 40px, $width: 36px);
}
42 changes: 42 additions & 0 deletions src/lib/viewers/controls/media/PlayPauseToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import noop from 'lodash/noop';
import IconPlay24 from '../icons/IconPlay24';
import IconPause24 from '../icons/IconPause24';
import MediaToggle from './MediaToggle';
import { decodeKeydown } from '../../../util';
import './PlayPauseToggle.scss';

export type Props = {
isPlaying?: boolean;
onPlayPause: (isPlaying: boolean) => void;
useHotkeys?: boolean;
};

export default function PlayPauseToggle({ isPlaying, onPlayPause = noop, useHotkeys }: Props): JSX.Element {
const Icon = isPlaying ? IconPause24 : IconPlay24;
const title = isPlaying ? __('media_pause') : __('media_play');

React.useEffect(() => {
const handleKeydown = (event: KeyboardEvent): void => {
const key = decodeKeydown(event);

if (key === 'k' || key === 'Shift+K' || key === 'Space') {
onPlayPause(!isPlaying);
}
};

if (useHotkeys) {
document.addEventListener('keydown', handleKeydown);
}

return (): void => {
document.removeEventListener('keydown', handleKeydown);
};
}, [isPlaying, onPlayPause, useHotkeys]);

return (
<MediaToggle className="bp-PlayPauseToggle" onClick={(): void => onPlayPause(!isPlaying)} title={title}>
<Icon />
</MediaToggle>
);
}
5 changes: 5 additions & 0 deletions src/lib/viewers/controls/media/SettingsControls.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '../styles';

.bp-SettingsControls-toggle {
@include bp-ControlButton($height: 40px, $width: 36px);
}
22 changes: 22 additions & 0 deletions src/lib/viewers/controls/media/SettingsControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import IconGear24 from '../icons/IconGear24';
import MediaToggle from './MediaToggle';
import './SettingsControls.scss';

export default function SettingsControls(): JSX.Element {
const [isVisible, setVisible] = React.useState(false);

const handleClick = (): void => {
setVisible(!isVisible);
};

return (
<div className="bp-SettingsControls">
<MediaToggle className="bp-SettingsControls-toggle" onClick={handleClick} title={__('media_settings')}>
<IconGear24 />
</MediaToggle>

{/* TODO: Add settings popup(s) */}
</div>
);
}
22 changes: 22 additions & 0 deletions src/lib/viewers/controls/media/VolumeControls.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import '../styles';

.bp-VolumeControls {
display: flex;
align-items: center;
}

.bp-VolumeControls-flyout {
display: flex;
align-items: center;
width: 0;
overflow: hidden;
transition: width 200ms ease-in-out;

&.bp-is-open {
width: 100px;
}
}

.bp-VolumeControls-toggle {
@include bp-ControlButton($height: 40px, $width: 36px);
}
Loading

0 comments on commit 4e9fd42

Please sign in to comment.