Skip to content

Commit

Permalink
feat(controls): Add media settings control item components (#1341)
Browse files Browse the repository at this point in the history
Co-authored-by: Mingze Xiao <mxiao@box.com>
  • Loading branch information
jstoffan and Mingze Xiao authored Mar 15, 2021
1 parent fcbca02 commit 81ae0c4
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import './styles';

.bp-MediaSettingsMenuBack {
@include bp-MediaSettingsRow;

&:hover {
background-color: $hover-blue-background;

.bp-MediaSettingsMenuBack-label {
color: lighten($blue-steel, 50%);
}
}

.bp-is-focused &:focus {
background-color: $box-blue;

.bp-MediaSettingsMenuBack-arrow {
fill: $white;
}

.bp-MediaSettingsMenuBack-label {
color: $white;
}
}
}

.bp-MediaSettingsMenuBack-arrow {
@include bp-MediaSettingsRow-cell;
}

.bp-MediaSettingsMenuBack-label {
@include bp-MediaSettingsRow-label;

text-align: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import IconArrowLeft24 from '../../icons/IconArrowLeft24';
import MediaSettingsContext, { Menu } from './MediaSettingsContext';
import { decodeKeydown } from '../../../../util';
import './MediaSettingsMenuBack.scss';

export type Props = {
label: string;
};
export type Ref = HTMLDivElement;

function MediaSettingsMenuBack({ label }: Props, ref: React.Ref<Ref>): JSX.Element {
const { setActiveMenu } = React.useContext(MediaSettingsContext);

const handleClick = (): void => {
setActiveMenu(Menu.MAIN);
};

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

if (key !== 'ArrowLeft' && key !== 'Enter' && key !== 'Space') {
return;
}

setActiveMenu(Menu.MAIN);
};

return (
<div
ref={ref}
className="bp-MediaSettingsMenuBack"
onClick={handleClick}
onKeyDown={handleKeydown}
role="menuitem"
tabIndex={0}
>
<div className="bp-MediaSettingsMenuBack-arrow">
<IconArrowLeft24 height={18} width={18} />
</div>
<div aria-label={label} className="bp-MediaSettingsMenuBack-label">
{label}
</div>
</div>
);
}

export default React.forwardRef(MediaSettingsMenuBack);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@import './styles';

.bp-MediaSettingsMenuItem {
@include bp-MediaSettingsRow;

&:hover {
background-color: $hover-blue-background;

.bp-MediaSettingsMenuItem-label {
color: lighten($blue-steel, 50%);
}

.bp-MediaSettingsMenuItem-value {
color: $blue-steel;
}
}

.bp-is-focused &:focus {
background-color: $box-blue;

.bp-MediaSettingsMenuItem-arrow {
fill: $white;
}

.bp-MediaSettingsMenuItem-label,
.bp-MediaSettingsMenuItem-value {
color: $white;
}
}
}

.bp-MediaSettingsMenuItem-arrow {
@include bp-MediaSettingsRow-cell;
}

.bp-MediaSettingsMenuItem-label {
@include bp-MediaSettingsRow-label;
}

.bp-MediaSettingsMenuItem-value {
@include bp-MediaSettingsRow-value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import classNames from 'classnames';
import IconArrowRight24 from '../../icons/IconArrowRight24';
import MediaSettingsContext, { Menu } from './MediaSettingsContext';
import { decodeKeydown } from '../../../../util';
import './MediaSettingsMenuItem.scss';

export type Props = {
className?: string;
label: string;
target: Menu;
value: string;
};
export type Ref = HTMLDivElement;

function MediaSettingsMenuItem(props: Props, ref: React.Ref<Ref>): JSX.Element {
const { className, label, target, value } = props;
const { setActiveMenu } = React.useContext(MediaSettingsContext);

const handleClick = (): void => {
setActiveMenu(target);
};

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

if (key !== 'ArrowRight' && key !== 'Enter' && key !== 'Space') {
return;
}

setActiveMenu(target);
};

return (
<div
ref={ref}
aria-haspopup="true"
className={classNames('bp-MediaSettingsMenuItem', className)}
onClick={handleClick}
onKeyDown={handleKeydown}
role="menuitem"
tabIndex={0}
>
<div aria-label={label} className="bp-MediaSettingsMenuItem-label">
{label}
</div>
<div className="bp-MediaSettingsMenuItem-value">{value}</div>
<div className="bp-MediaSettingsMenuItem-arrow">
<IconArrowRight24 height={18} width={18} />
</div>
</div>
);
}

export default React.forwardRef(MediaSettingsMenuItem);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@import './styles';

.bp-MediaSettingsRadioItem {
@include bp-MediaSettingsRow;

&.bp-is-selected {
.bp-MediaSettingsRadioItem-check-icon {
visibility: visible;
}

.bp-MediaSettingsRadioItem-value {
color: $box-blue;
}
}

&:hover {
background-color: $hover-blue-background;

.bp-MediaSettingsRadioItem-check-icon {
fill: $blue-steel;
}

.bp-MediaSettingsRadioItem-value {
color: $blue-steel;
}
}

.bp-is-focused &:focus {
background-color: $box-blue;

.bp-MediaSettingsRadioItem-check-icon {
fill: $white;
}

.bp-MediaSettingsRadioItem-value {
color: $white;
}
}
}

.bp-MediaSettingsRadioItem-check {
@include bp-MediaSettingsRow-cell;
}

.bp-MediaSettingsRadioItem-check-icon {
visibility: hidden;
}

.bp-MediaSettingsRadioItem-value {
@include bp-MediaSettingsRow-value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import classNames from 'classnames';
import IconCheckMark24 from '../../icons/IconCheckMark24';
import { decodeKeydown } from '../../../../util';
import './MediaSettingsRadioItem.scss';

export type Props<V extends Value> = {
className?: string;
isSelected?: boolean;
label?: string;
onChange: (value: V) => void;
value: V;
};
export type Ref = HTMLDivElement;
export type Value = boolean | number | string;

function MediaSettingsRadioItem<V extends Value>(props: Props<V>, ref: React.Ref<Ref>): JSX.Element {
const { className, isSelected, label, onChange, value } = props;

const handleClick = (): void => {
onChange(value);
};

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

if (key !== 'ArrowLeft' && key !== 'Enter' && key !== 'Space') {
return;
}

onChange(value);
};

return (
<div
ref={ref}
aria-checked={isSelected ? 'true' : 'false'}
className={classNames('bp-MediaSettingsRadioItem', className, {
'bp-is-selected': isSelected,
})}
onClick={handleClick}
onKeyDown={handleKeydown}
role="menuitemradio"
tabIndex={0}
>
<div className="bp-MediaSettingsRadioItem-check">
<IconCheckMark24 className="bp-MediaSettingsRadioItem-check-icon" height={16} width={16} />
</div>
<div className="bp-MediaSettingsRadioItem-value">{label || value}</div>
</div>
);
}

export default React.forwardRef(MediaSettingsRadioItem) as typeof MediaSettingsRadioItem;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import MediaSettingsContext, { Context, Menu } from '../MediaSettingsContext';
describe('MediaSettingsContext', () => {
const getContext = (): Context => ({
activeMenu: Menu.MAIN,
activeRect: undefined,
setActiveMenu: jest.fn(),
setActiveRect: jest.fn(),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import IconArrowLeft24 from '../../../icons/IconArrowLeft24';
import MediaSettingsContext, { Context } from '../MediaSettingsContext';
import MediaSettingsMenuBack from '../MediaSettingsMenuBack';

describe('MediaSettingsMenuBack', () => {
const getContext = (): Partial<Context> => ({ setActiveMenu: jest.fn() });
const getWrapper = (props = {}, context = {}): ReactWrapper =>
mount(<MediaSettingsMenuBack label="Autoplay" {...props} />, {
wrappingComponent: MediaSettingsContext.Provider,
wrappingComponentProps: { value: context },
});

describe('event handlers', () => {
test('should set the active menu when clicked', () => {
const context = getContext();
const wrapper = getWrapper({}, context);

wrapper.simulate('click');

expect(context.setActiveMenu).toBeCalled();
});

test.each`
key | calledTimes
${'ArrowLeft'} | ${1}
${'Enter'} | ${1}
${'Escape'} | ${0}
${'Space'} | ${1}
`('should set the active menu $calledTimes times when $key is pressed', ({ key, calledTimes }) => {
const context = getContext();
const wrapper = getWrapper({}, context);

wrapper.simulate('keydown', { key });

expect(context.setActiveMenu).toBeCalledTimes(calledTimes);
});
});

describe('render', () => {
test('should return a valid wrapper', () => {
const wrapper = getWrapper();

expect(wrapper.getDOMNode()).toHaveClass('bp-MediaSettingsMenuBack');
expect(wrapper.find('.bp-MediaSettingsMenuBack-label').contains('Autoplay')).toBe(true);
expect(wrapper.exists(IconArrowLeft24)).toBe(true);
});
});
});
Loading

0 comments on commit 81ae0c4

Please sign in to comment.