feat(toolbox): introduce ToolboxItem
This abstraction represents an action which can go anywhere in a toolbox (be that the main toolbar or the overflow menu) and it's platform independent. It does not depend on Redux, thus making it stateless, which facilitates its use in stateful button implementations as well as stateless ones.
This commit is contained in:
parent
52da5010cc
commit
8d94cc5cb2
|
@ -0,0 +1,198 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
export type Styles = {
|
||||
|
||||
/**
|
||||
* Style for the item's icon.
|
||||
*/
|
||||
iconStyle: Object,
|
||||
|
||||
/**
|
||||
* Style for the item itself.
|
||||
*/
|
||||
style: Object,
|
||||
|
||||
/**
|
||||
* Color for the item underlay (shows when clicked).
|
||||
*/
|
||||
underlayColor: string
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* A succinct description of what the item does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
*/
|
||||
accessibilityLabel: string,
|
||||
|
||||
/**
|
||||
* Whether this item is disabled or not. When disabled, clicking an the item
|
||||
* has no effect, and it may reflect on its style.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The name of the icon of this {@code ToolboxItem}.
|
||||
*/
|
||||
iconName: string,
|
||||
|
||||
/**
|
||||
* The text associated with this item. When `showLabel` is set to
|
||||
* {@code true}, it will be displayed alongside the icon.
|
||||
*/
|
||||
label: string,
|
||||
|
||||
/**
|
||||
* On click handler.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* Whether to show the label or not.
|
||||
*/
|
||||
showLabel: boolean,
|
||||
|
||||
/**
|
||||
* Collection of styles for the item. Used only on native.
|
||||
*/
|
||||
styles: ?Styles,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: ?Function,
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip. Used only on web.
|
||||
*/
|
||||
tooltip: string,
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the
|
||||
* item. Used only on web.
|
||||
*/
|
||||
tooltipPosition: string,
|
||||
|
||||
/**
|
||||
* Whether this item is visible or not.
|
||||
*/
|
||||
visible: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract (base) class for an item in {@link Toolbox}. The item can be located
|
||||
* anywhere in the {@link Toolbox}, it will morph its shape to accommodate it.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class AbstractToolboxItem<P : Props> extends Component<P> {
|
||||
/**
|
||||
* Default values for {@code AbstractToolboxItem} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
disabled: false,
|
||||
label: '',
|
||||
showLabel: false,
|
||||
t: undefined,
|
||||
tooltip: '',
|
||||
tooltipPosition: 'top',
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractToolboxItem} instance.
|
||||
*
|
||||
* @param {Object} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractToolboxItem} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper property to get the item label. If a translation function was
|
||||
* provided then it will be translated using it.
|
||||
*
|
||||
* @protected
|
||||
* @returns {string}
|
||||
*/
|
||||
get _label() {
|
||||
return this._maybeTranslateAttribute(this.props.label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper property to get the item tooltip. If a translation function was
|
||||
* provided then it will be translated using it.
|
||||
*
|
||||
* @protected
|
||||
* @returns {string}
|
||||
*/
|
||||
get _tooltip() {
|
||||
return this._maybeTranslateAttribute(this.props.tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to translate the given string, if a translation
|
||||
* function is available.
|
||||
*
|
||||
* @param {string} text - What needs translating.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_maybeTranslateAttribute(text) {
|
||||
const { t } = this.props;
|
||||
|
||||
if (typeof t === 'function') {
|
||||
return t(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
_onClick: (*) => void;
|
||||
|
||||
/**
|
||||
* Handles clicking/pressing this {@code AbstractToolboxItem} by
|
||||
* forwarding the event to the {@code onClick} prop of this instance if any.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(...args) {
|
||||
const { disabled, onClick } = this.props;
|
||||
|
||||
!disabled && onClick && onClick(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rendering of the actual item.
|
||||
*
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderItem() {
|
||||
// To be implemented by a subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (!this.props.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._renderItem();
|
||||
}
|
||||
}
|
|
@ -127,30 +127,23 @@ class Toolbox extends Component<Props> {
|
|||
* button to get styles for.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* iconName: string,
|
||||
* iconStyle: Object,
|
||||
* style: Object
|
||||
* }}
|
||||
*/
|
||||
_getMuteButtonStyles(mediaType) {
|
||||
let iconName;
|
||||
let iconStyle;
|
||||
let style;
|
||||
|
||||
if (this.props[`_${mediaType}Muted`]) {
|
||||
iconName = `${mediaType}MutedIcon`;
|
||||
iconStyle = styles.whitePrimaryToolbarButtonIcon;
|
||||
style = styles.whitePrimaryToolbarButton;
|
||||
} else {
|
||||
iconName = `${mediaType}Icon`;
|
||||
iconStyle = styles.primaryToolbarButtonIcon;
|
||||
style = styles.primaryToolbarButton;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// $FlowExpectedError
|
||||
iconName: this[iconName],
|
||||
iconStyle,
|
||||
style
|
||||
};
|
||||
|
@ -174,9 +167,9 @@ class Toolbox extends Component<Props> {
|
|||
key = 'primaryToolbar'
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.primaryToolbar }>
|
||||
<AudioMuteButton buttonStyles = { audioButtonStyles } />
|
||||
<AudioMuteButton styles = { audioButtonStyles } />
|
||||
<HangupButton />
|
||||
<VideoMuteButton buttonStyles = { videoButtonStyles } />
|
||||
<VideoMuteButton styles = { videoButtonStyles } />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
@ -263,20 +256,6 @@ class Toolbox extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional properties for various icons, which are now platform-dependent.
|
||||
* This is done to have common logic of generating styles for web and native.
|
||||
* TODO As soon as we have common font sets for web and native, this will no
|
||||
* longer be required.
|
||||
*/
|
||||
// $FlowExpectedError
|
||||
Object.assign(Toolbox.prototype, {
|
||||
audioIcon: 'microphone',
|
||||
audioMutedIcon: 'mic-disabled',
|
||||
videoIcon: 'camera',
|
||||
videoMutedIcon: 'camera-disabled'
|
||||
});
|
||||
|
||||
/**
|
||||
* Maps redux actions to {@link Toolbox}'s React {@code Component} props.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
|
||||
import { Icon } from '../../base/font-icons';
|
||||
|
||||
import AbstractToolboxItem from './AbstractToolboxItem';
|
||||
import type { Props } from './AbstractToolboxItem';
|
||||
|
||||
/**
|
||||
* Native implementation of {@code AbstractToolboxItem}.
|
||||
*/
|
||||
export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
||||
/**
|
||||
* Transform the given (web) icon name into a name that works with
|
||||
* {@code Icon}.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getIconName() {
|
||||
const { iconName } = this.props;
|
||||
|
||||
return iconName.replace('icon-', '').split(' ')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rendering of the actual item.
|
||||
*
|
||||
* TODO: currently no handling for labels is implemented.
|
||||
*
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderItem() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
disabled,
|
||||
onClick,
|
||||
styles
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onClick }
|
||||
style = { styles && styles.style }
|
||||
underlayColor = { styles && styles.underlayColor } >
|
||||
<Icon
|
||||
name = { this._getIconName() }
|
||||
style = { styles && styles.iconStyle } />
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// @flow
|
||||
|
||||
import Tooltip from '@atlaskit/tooltip';
|
||||
import React from 'react';
|
||||
|
||||
import AbstractToolboxItem from './AbstractToolboxItem';
|
||||
import type { Props } from './AbstractToolboxItem';
|
||||
|
||||
/**
|
||||
* Web implementation of {@code AbstractToolboxItem}.
|
||||
*/
|
||||
export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
||||
_label: string;
|
||||
_tooltip: string;
|
||||
|
||||
/**
|
||||
* Handles rendering of the actual item. If the label is being shown, which
|
||||
* is controlled with the `showLabel` prop, the item is rendered for its
|
||||
* display in an overflow menu, otherwise it will only have an icon, which
|
||||
* can be displayed on any toolbar.
|
||||
*
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderItem() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
onClick,
|
||||
showLabel
|
||||
} = this.props;
|
||||
const props = {
|
||||
'aria-label': accessibilityLabel,
|
||||
className: showLabel ? 'overflow-menu-item' : 'toolbox-button',
|
||||
onClick
|
||||
};
|
||||
const elementType = showLabel ? 'li' : 'div';
|
||||
// eslint-disable-next-line no-extra-parens
|
||||
const children = (
|
||||
|
||||
// $FlowFixMe
|
||||
<React.Fragment>
|
||||
{ this._renderIcon() }
|
||||
{ showLabel && this._label }
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return React.createElement(elementType, props, children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render the item's icon.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderIcon() {
|
||||
const { iconName, tooltipPosition, showLabel } = this.props;
|
||||
const icon = <i className = { iconName } />;
|
||||
const elementType = showLabel ? 'span' : 'div';
|
||||
const className
|
||||
= showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon';
|
||||
const iconWrapper
|
||||
= React.createElement(elementType, { className }, icon);
|
||||
const tooltip = this._tooltip;
|
||||
const useTooltip = !showLabel && tooltip && tooltip.length > 0;
|
||||
|
||||
if (useTooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
description = { tooltip }
|
||||
position = { tooltipPosition }>
|
||||
{ iconWrapper }
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return iconWrapper;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue