diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsContext.ts b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsContext.ts new file mode 100644 index 000000000..d21655f08 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsContext.ts @@ -0,0 +1,23 @@ +import React from 'react'; +import noop from 'lodash/noop'; + +export type Context = { + activeMenu: Menu; + activeRect?: Rect; + setActiveMenu: (menu: Menu) => void; + setActiveRect: (activeRect: Rect) => void; +}; + +export enum Menu { + MAIN = 'main', + AUTOPLAY = 'autoplay', + RATE = 'rate', +} + +export type Rect = ClientRect; + +export default React.createContext({ + activeMenu: Menu.MAIN, + setActiveMenu: noop, + setActiveRect: noop, +}); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsControls.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsControls.tsx new file mode 100644 index 000000000..e4358bc2d --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsControls.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import classNames from 'classnames'; +import MediaSettingsContext, { Menu, Rect } from './MediaSettingsContext'; +import MediaSettingsFlyout from './MediaSettingsFlyout'; +import MediaSettingsToggle, { Ref as MediaSettingsToggleRef } from './MediaSettingsToggle'; +import { decodeKeydown } from '../../../../util'; + +export type Props = React.PropsWithChildren<{ + className?: string; +}>; + +export default function MediaSettingsControls({ children, className, ...rest }: Props): JSX.Element | null { + const [activeMenu, setActiveMenu] = React.useState(Menu.MAIN); + const [activeRect, setActiveRect] = React.useState(); + const [isFocused, setIsFocused] = React.useState(false); + const [isOpen, setIsOpen] = React.useState(false); + const buttonElRef = React.useRef(null); + const controlsElRef = React.useRef(null); + const resetControls = React.useCallback(() => { + setActiveMenu(Menu.MAIN); + setActiveRect(undefined); + setIsFocused(false); + setIsOpen(false); + }, []); + + const handleClick = (): void => { + setActiveMenu(Menu.MAIN); + setActiveRect(undefined); + setIsFocused(false); + setIsOpen(!isOpen); + }; + + const handleKeyDown = (event: React.KeyboardEvent): void => { + const key = decodeKeydown(event); + + if (key === 'Enter' || key === 'Space' || key === 'Tab' || key.indexOf('Arrow') >= 0) { + setIsFocused(true); // User has interacted with the menu via keyboard directly + } + + if (key === 'Escape') { + resetControls(); + + if (buttonElRef.current) { + buttonElRef.current.focus(); // Prevent focus from falling back to the body on flyout close + } + } + + event.stopPropagation(); + }; + + React.useEffect(() => { + const handleDocumentClick = ({ target }: MouseEvent): void => { + const { current: controlsEl } = controlsElRef; + + if (controlsEl && controlsEl.contains(target as Node)) { + return; + } + + resetControls(); + }; + + document.addEventListener('click', handleDocumentClick); + + return (): void => { + document.removeEventListener('click', handleDocumentClick); + }; + }, [resetControls]); + + return ( +
+ + + {children} + +
+ ); +} diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsFlyout.scss b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsFlyout.scss new file mode 100644 index 000000000..2bdd58d24 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsFlyout.scss @@ -0,0 +1,29 @@ +@import '../styles'; + +.bp-MediaSettingsFlyout { + position: absolute; + top: -5px; + right: 0; + display: none; + max-width: 400px; + max-height: 210px; + overflow-x: hidden; + overflow-y: auto; // Prevent scrollbar from showing on hover in IE/Edge + color: $fours; + font-size: 10px; + line-height: normal; + background-color: $white; + border-radius: 2px; + box-shadow: 0 0 1px 1px $sf-fog; // Prevent overflow due to global box-sizing: border-box + transform: translateY(-100%); + transition: width 200ms, height 200ms; + -ms-overflow-style: -ms-autohiding-scrollbar; + + &.bp-is-open { + display: inline-block; + } + + &.bp-is-transitioning { + overflow-y: hidden; // Hide scrollbar during menu -> submenu transition + } +} diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsFlyout.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsFlyout.tsx new file mode 100644 index 000000000..df9fd59cc --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsFlyout.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import classNames from 'classnames'; +import MediaSettingsContext from './MediaSettingsContext'; +import './MediaSettingsFlyout.scss'; + +export type Props = React.PropsWithChildren<{ + className?: string; + isOpen: boolean; +}>; + +export default function MediaSettingsFlyout({ children, className, isOpen }: Props): JSX.Element { + const [isTransitioning, setIsTransitioning] = React.useState(false); + const flyoutElRef = React.useRef(null); + const { activeRect } = React.useContext(MediaSettingsContext); + const { height, width } = activeRect || { height: 'auto', width: 'auto' }; + + React.useEffect(() => { + const { current: flyoutEl } = flyoutElRef; + const handleTransitionEnd = (): void => setIsTransitioning(false); + const handleTransitionStart = (): void => setIsTransitioning(true); + + if (flyoutEl) { + flyoutEl.addEventListener('transitionend', handleTransitionEnd); + flyoutEl.addEventListener('transitionstart', handleTransitionStart); + } + + return (): void => { + if (flyoutEl) { + flyoutEl.removeEventListener('transitionend', handleTransitionEnd); + flyoutEl.removeEventListener('transitionstart', handleTransitionStart); + } + }; + }, []); + + return ( +
+ {isOpen && children} +
+ ); +} diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsMenu.scss b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsMenu.scss new file mode 100644 index 000000000..cabab2ffb --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsMenu.scss @@ -0,0 +1,8 @@ +.bp-MediaSettingsMenu { + display: none; + padding: 8px; + + &.bp-is-active { + display: table; + } +} diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsMenu.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsMenu.tsx new file mode 100644 index 000000000..17cc6ce74 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsMenu.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import classNames from 'classnames'; +import MediaSettingsContext, { Menu } from './MediaSettingsContext'; +import { decodeKeydown } from '../../../../util'; +import './MediaSettingsMenu.scss'; + +export type Props = React.PropsWithChildren<{ + className?: string; + name: Menu; +}>; + +export default function MediaSettingsMenu({ children, className, name }: Props): JSX.Element | null { + const [activeIndex, setActiveIndex] = React.useState(0); + const [activeItem, setActiveItem] = React.useState(null); + const { activeMenu, setActiveRect } = React.useContext(MediaSettingsContext); + const isActive = activeMenu === name; + const menuElRef = React.useRef(null); + + const handleKeyDown = (event: React.KeyboardEvent): void => { + const key = decodeKeydown(event); + const max = React.Children.toArray(children).length - 1; + + if (key === 'ArrowUp' && activeIndex > 0) { + setActiveIndex(activeIndex - 1); + } + + if (key === 'ArrowDown' && activeIndex < max) { + setActiveIndex(activeIndex + 1); + } + }; + + React.useEffect(() => { + const { current: menuEl } = menuElRef; + + if (menuEl && isActive) { + setActiveRect(menuEl.getBoundingClientRect()); + } + }, [isActive, setActiveRect]); + + React.useEffect(() => { + if (activeItem && isActive) { + activeItem.focus(); + } + }, [activeItem, isActive]); + + return ( +
+ {React.Children.map(children, (menuItem, menuIndex) => { + if (React.isValidElement(menuItem) && menuIndex === activeIndex) { + return React.cloneElement(menuItem, { ref: setActiveItem, ...menuItem.props }); + } + + return menuItem; + })} +
+ ); +} diff --git a/src/lib/viewers/controls/media/SettingsControls.scss b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsToggle.scss similarity index 57% rename from src/lib/viewers/controls/media/SettingsControls.scss rename to src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsToggle.scss index e52515f6d..af7b78607 100644 --- a/src/lib/viewers/controls/media/SettingsControls.scss +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsToggle.scss @@ -1,16 +1,16 @@ -@import './styles'; +@import '../styles'; -.bp-SettingsControls-toggle { +.bp-MediaSettingsToggle { @include bp-MediaButton; &.bp-is-open { - .bp-SettingsControls-toggle-icon { + .bp-MediaSettingsToggle-icon { transform: rotate(60deg); } } } -.bp-SettingsControls-toggle-icon { +.bp-MediaSettingsToggle-icon { transform: rotate(0); transition: transform 300ms ease; } diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsToggle.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsToggle.tsx new file mode 100644 index 000000000..64a5f3063 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/MediaSettingsToggle.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import classNames from 'classnames'; +import IconGear24 from '../../icons/IconGear24'; +import './MediaSettingsToggle.scss'; + +export type Props = { + isOpen: boolean; + onClick: () => void; +}; + +export type Ref = HTMLButtonElement; + +function MediaSettingsToggle({ isOpen, onClick }: Props, ref: React.Ref): JSX.Element { + return ( + + ); +} + +export default React.forwardRef(MediaSettingsToggle); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsContext-test.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsContext-test.tsx new file mode 100644 index 000000000..4449751cf --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsContext-test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import MediaSettingsContext, { Context, Menu } from '../MediaSettingsContext'; + +describe('MediaSettingsContext', () => { + const getContext = (): Context => ({ + activeMenu: Menu.MAIN, + activeRect: undefined, + setActiveMenu: jest.fn(), + setActiveRect: jest.fn(), + }); + const TestComponent = (): JSX.Element => ( +
{React.useContext(MediaSettingsContext).activeMenu}
+ ); + + test('should populate its context values', () => { + const wrapper = mount(, { + wrappingComponent: MediaSettingsContext.Provider, + wrappingComponentProps: { value: getContext() }, + }); + + expect(wrapper.text()).toBe(Menu.MAIN); + }); +}); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsControls-test.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsControls-test.tsx new file mode 100644 index 000000000..491bbbd35 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsControls-test.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount, ReactWrapper } from 'enzyme'; +import MediaSettingsControls from '../MediaSettingsControls'; +import MediaSettingsToggle from '../MediaSettingsToggle'; +import MediaSettingsFlyout from '../MediaSettingsFlyout'; + +describe('MediaSettingsControls', () => { + const getHostNode = (): HTMLDivElement => { + return document.body.appendChild(document.createElement('div')); + }; + const getWrapper = (props = {}): ReactWrapper => + mount(, { attachTo: getHostNode() }); + + describe('event handlers', () => { + test('should update the flyout and toggle button isOpen prop when clicked', () => { + const wrapper = getWrapper(); + + expect(wrapper.find(MediaSettingsFlyout).prop('isOpen')).toBe(false); + expect(wrapper.find(MediaSettingsToggle).prop('isOpen')).toBe(false); + + wrapper.find(MediaSettingsToggle).simulate('click'); + + expect(wrapper.find(MediaSettingsFlyout).prop('isOpen')).toBe(true); + expect(wrapper.find(MediaSettingsToggle).prop('isOpen')).toBe(true); + }); + + test.each` + key | isFocused + ${'1'} | ${false} + ${'A'} | ${false} + ${'ArrowDown'} | ${true} + ${'ArrowLeft'} | ${true} + ${'ArrowRight'} | ${true} + ${'ArrowUp'} | ${true} + ${'Enter'} | ${true} + ${'Space'} | ${true} + ${'Tab'} | ${true} + `('should update the focused state to $isFocused if $key is pressed', ({ key, isFocused }) => { + const wrapper = getWrapper(); + + expect(wrapper.childAt(0).hasClass('bp-is-focused')).toBe(false); + + act(() => { + wrapper.simulate('keydown', { key }); + }); + wrapper.update(); + + expect(wrapper.childAt(0).hasClass('bp-is-focused')).toBe(isFocused); + }); + + test('should reset the parent context when a click is detected outside the controls', () => { + const wrapper = getWrapper(); + const getEvent = (target: HTMLElement): MouseEvent => { + const event = new MouseEvent('click'); + Object.defineProperty(event, 'target', { enumerable: true, value: target }); + return event; + }; + + wrapper.find(MediaSettingsToggle).simulate('click'); // Open the controls + expect(wrapper.find(MediaSettingsToggle).prop('isOpen')).toBe(true); + + act(() => { + document.dispatchEvent(getEvent(document.body)); // Click outside the controls + }); + wrapper.update(); + expect(wrapper.find(MediaSettingsToggle).prop('isOpen')).toBe(false); + + wrapper.find(MediaSettingsToggle).simulate('click'); // Re-open the controls + expect(wrapper.find(MediaSettingsToggle).prop('isOpen')).toBe(true); + + wrapper.find(MediaSettingsFlyout).simulate('click'); // Click within the controls + expect(wrapper.find(MediaSettingsToggle).prop('isOpen')).toBe(true); + }); + + test('should stop propagation on all keydown events to prevent triggering global event listeners', () => { + const wrapper = getWrapper(); + const event = { stopPropagation: jest.fn() }; // Key is not relevant + + act(() => { + wrapper.simulate('keydown', event); + }); + wrapper.update(); + + expect(event.stopPropagation).toBeCalled(); + }); + }); + + describe('render', () => { + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + + expect(wrapper.getDOMNode()).toHaveClass('bp-MediaSettingsControls'); + expect(wrapper.exists(MediaSettingsFlyout)).toBe(true); + expect(wrapper.exists(MediaSettingsToggle)).toBe(true); + }); + }); +}); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsFlyout-test.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsFlyout-test.tsx new file mode 100644 index 000000000..ac8502a8d --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsFlyout-test.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount, ReactWrapper } from 'enzyme'; +import MediaSettingsContext, { Context } from '../MediaSettingsContext'; +import MediaSettingsFlyout from '../MediaSettingsFlyout'; + +describe('MediaSettingsFlyout', () => { + const getContext = (): Partial => ({ activeRect: undefined }); + const getWrapper = (props = {}, context = getContext()): ReactWrapper => + mount(, { + wrappingComponent: MediaSettingsContext.Provider, + wrappingComponentProps: { value: context }, + }); + + describe('event handlers', () => { + test('should set classes based on the transitionstart/end events', () => { + const wrapper = getWrapper(); + expect(wrapper.getDOMNode()).not.toHaveClass('bp-is-transitioning'); + + act(() => { + wrapper.getDOMNode().dispatchEvent(new Event('transitionstart')); + }); + expect(wrapper.getDOMNode()).toHaveClass('bp-is-transitioning'); + + act(() => { + wrapper.getDOMNode().dispatchEvent(new Event('transitionend')); + }); + expect(wrapper.getDOMNode()).not.toHaveClass('bp-is-transitioning'); + }); + }); + + describe('render', () => { + test.each([true, false])('should set classes based on the isOpen prop %s', isOpen => { + const wrapper = getWrapper({ isOpen }); + + expect(wrapper.childAt(0).hasClass('bp-is-open')).toBe(isOpen); + }); + + test('should set styles based on the activeRect, if present', () => { + const activeRect = { bottom: 0, left: 0, height: 100, right: 0, top: 0, width: 100 }; + const wrapper = getWrapper({}, { activeRect }); + + expect(wrapper.childAt(0).prop('style')).toEqual({ + height: 100, + width: 100, + }); + }); + + test('should set styles based on defaults if activeRect is not present', () => { + const wrapper = getWrapper(); + + expect(wrapper.childAt(0).prop('style')).toEqual({ + height: 'auto', + width: 'auto', + }); + }); + + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + + expect(wrapper.getDOMNode()).toHaveClass('bp-MediaSettingsFlyout'); + }); + }); +}); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsMenu-test.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsMenu-test.tsx new file mode 100644 index 000000000..73323c072 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsMenu-test.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount, ReactWrapper } from 'enzyme'; +import MediaSettingsContext, { Context, Menu } from '../MediaSettingsContext'; +import MediaSettingsMenu from '../MediaSettingsMenu'; + +describe('MediaSettingsMenu', () => { + const getContext = (overrides = {}): Partial => ({ + activeMenu: Menu.MAIN, + setActiveRect: jest.fn(), + ...overrides, + }); + const getHostNode = (): HTMLDivElement => { + return document.body.appendChild(document.createElement('div')); + }; + const getWrapper = (props = {}, context = getContext()): ReactWrapper => + mount( + +
+
+
+ , + { + attachTo: getHostNode(), + wrappingComponent: MediaSettingsContext.Provider, + wrappingComponentProps: { value: context }, + }, + ); + + describe('event handlers', () => { + test('should focus the active menu index based on the arrow keys', () => { + const wrapper = getWrapper(); + const simulateKey = (key: string): void => { + act(() => { + wrapper.simulate('keydown', { key }); + }); + wrapper.update(); + }; + + expect(wrapper.find('[data-testid="test1"]').getDOMNode()).toHaveFocus(); // Default case on mount + + simulateKey('ArrowDown'); + expect(wrapper.find('[data-testid="test2"]').getDOMNode()).toHaveFocus(); + + simulateKey('ArrowDown'); + expect(wrapper.find('[data-testid="test3"]').getDOMNode()).toHaveFocus(); + + simulateKey('ArrowDown'); + expect(wrapper.find('[data-testid="test3"]').getDOMNode()).toHaveFocus(); // Increment stops at list end + + simulateKey('ArrowUp'); + expect(wrapper.find('[data-testid="test2"]').getDOMNode()).toHaveFocus(); + + simulateKey('ArrowUp'); + expect(wrapper.find('[data-testid="test1"]').getDOMNode()).toHaveFocus(); + + simulateKey('ArrowUp'); + expect(wrapper.find('[data-testid="test1"]').getDOMNode()).toHaveFocus(); // Decrement stops at list start + }); + }); + + describe('lifecycle', () => { + test('should update the active rect on mount', () => { + const context = getContext(); + const wrapper = getWrapper({}, context); + + expect(context.setActiveRect).toBeCalledWith(wrapper.getDOMNode().getBoundingClientRect()); + }); + }); + + describe('render', () => { + test('should set classes based on the active menu', () => { + const context = getContext({ activeMenu: Menu.MAIN }); + const wrapper = getWrapper({ name: Menu.MAIN }, context); + + expect(wrapper.childAt(0).hasClass('bp-is-active')).toBe(true); + + context.activeMenu = Menu.AUTOPLAY; + wrapper.setProps({}); // Force re-render + wrapper.update(); + + expect(wrapper.childAt(0).hasClass('bp-is-active')).toBe(false); + }); + + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + const element = wrapper.getDOMNode(); + + expect(element).toHaveClass('bp-MediaSettingsMenu'); + expect(element).toHaveAttribute('role', 'menu'); + }); + }); +}); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsToggle-test.tsx b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsToggle-test.tsx new file mode 100644 index 000000000..c90b87236 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/__tests__/MediaSettingsToggle-test.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import IconGear24 from '../../../icons/IconGear24'; +import MediaSettingsToggle from '../MediaSettingsToggle'; + +describe('MediaSettingsToggle', () => { + const getWrapper = (props = {}): ShallowWrapper => + shallow(); + + describe('render', () => { + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + + expect(wrapper.hasClass('bp-MediaSettingsToggle')).toBe(true); + expect(wrapper.exists(IconGear24)).toBe(true); + expect(wrapper.prop('title')).toBe('Settings'); + }); + + test.each([true, false])('should add or remove class based on isOpen prop', isOpen => { + const wrapper = getWrapper({ isOpen }); + + expect(wrapper.hasClass('bp-is-open')).toBe(isOpen); + }); + }); +}); diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/_styles.scss b/src/lib/viewers/controls/media/MediaSettingsControls/_styles.scss new file mode 100644 index 000000000..4e2c0e378 --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/_styles.scss @@ -0,0 +1,34 @@ +@import '../styles'; + +@mixin bp-MediaSettingsRow { + display: table-row; + overflow: hidden; + white-space: nowrap; + outline: 0 none; + cursor: pointer; +} + +@mixin bp-MediaSettingsRow-cell { + display: table-cell; + vertical-align: middle; +} + +@mixin bp-MediaSettingsRow-label { + @include bp-MediaSettingsRow-cell; + + padding: 6px; + color: $downtown-grey; + font-weight: normal; + font-size: 11px; + text-align: left; + text-transform: uppercase; +} + +@mixin bp-MediaSettingsRow-value { + @include bp-MediaSettingsRow-cell; + + padding: 6px 6px 6px 10px; + color: $sunset-grey; + font-size: 12px; + text-align: left; +} diff --git a/src/lib/viewers/controls/media/MediaSettingsControls/index.ts b/src/lib/viewers/controls/media/MediaSettingsControls/index.ts new file mode 100644 index 000000000..95d740c1e --- /dev/null +++ b/src/lib/viewers/controls/media/MediaSettingsControls/index.ts @@ -0,0 +1,3 @@ +export { default } from './MediaSettingsControls'; +export * from './MediaSettingsContext'; +export * from './MediaSettingsControls'; diff --git a/src/lib/viewers/controls/media/SettingsControls.tsx b/src/lib/viewers/controls/media/SettingsControls.tsx deleted file mode 100644 index 11c228384..000000000 --- a/src/lib/viewers/controls/media/SettingsControls.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import IconGear24 from '../icons/IconGear24'; -import MediaToggle from './MediaToggle'; -import './SettingsControls.scss'; - -export default function SettingsControls(): JSX.Element { - const [isOpen, setOpen] = React.useState(false); - - const handleClick = (): void => { - setOpen(!isOpen); - }; - - return ( -
- - - - - {/* TODO: Add settings popup(s) */} -
- ); -} diff --git a/src/lib/viewers/controls/media/__tests__/SettingsControls-test.tsx b/src/lib/viewers/controls/media/__tests__/SettingsControls-test.tsx deleted file mode 100644 index db5925305..000000000 --- a/src/lib/viewers/controls/media/__tests__/SettingsControls-test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import IconGear24 from '../../icons/IconGear24'; -import MediaToggle from '../MediaToggle'; -import SettingsControls from '../SettingsControls'; - -describe('SettingsControls', () => { - const getWrapper = (props = {}): ShallowWrapper => shallow(); - - describe('event handlers', () => { - test('should update the toggle button when clicked', () => { - const wrapper = getWrapper(); - const isOpen = (): boolean => wrapper.find(MediaToggle).hasClass('bp-is-open'); - - expect(isOpen()).toBe(false); - - wrapper.find(MediaToggle).simulate('click'); - - expect(isOpen()).toBe(true); - }); - }); - - describe('render', () => { - test('should return a valid wrapper', () => { - const wrapper = getWrapper(); - - expect(wrapper.hasClass('bp-SettingsControls')).toBe(true); - expect(wrapper.exists(MediaToggle)).toBe(true); - expect(wrapper.exists(IconGear24)).toBe(true); - }); - }); -}); diff --git a/src/lib/viewers/media/MP3Controls.tsx b/src/lib/viewers/media/MP3Controls.tsx index 00e3343a4..7a22ed89b 100644 --- a/src/lib/viewers/media/MP3Controls.tsx +++ b/src/lib/viewers/media/MP3Controls.tsx @@ -1,7 +1,6 @@ import React from 'react'; import DurationLabels, { Props as DurationLabelsProps } from '../controls/media/DurationLabels'; import PlayPauseToggle, { Props as PlayControlsProps } from '../controls/media/PlayPauseToggle'; -import SettingsControls from '../controls/media/SettingsControls'; import TimeControls, { Props as TimeControlsProps } from '../controls/media/TimeControls'; import VolumeControls, { Props as VolumeControlsProps } from '../controls/media/VolumeControls'; import './MP3Controls.scss'; @@ -35,9 +34,7 @@ export default function MP3Controls({
-
- -
+
{/* MP3 Settings Controls */}
);