diff --git a/react/features/toolbox/components/AbstractToolboxItem.js b/react/features/toolbox/components/AbstractToolboxItem.js new file mode 100644 index 0000000000..373688fbca --- /dev/null +++ b/react/features/toolbox/components/AbstractToolboxItem.js @@ -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

extends Component

{ + /** + * 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(); + } +} diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js index 0950018f3b..289eb46e10 100644 --- a/react/features/toolbox/components/Toolbox.native.js +++ b/react/features/toolbox/components/Toolbox.native.js @@ -127,30 +127,23 @@ class Toolbox extends Component { * 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 { key = 'primaryToolbar' pointerEvents = 'box-none' style = { styles.primaryToolbar }> - + - + ); @@ -263,20 +256,6 @@ class Toolbox extends Component { } } -/** - * 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. * diff --git a/react/features/toolbox/components/ToolboxItem.native.js b/react/features/toolbox/components/ToolboxItem.native.js new file mode 100644 index 0000000000..eda1a77075 --- /dev/null +++ b/react/features/toolbox/components/ToolboxItem.native.js @@ -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 { + /** + * 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 ( + + + + ); + } +} diff --git a/react/features/toolbox/components/ToolboxItem.web.js b/react/features/toolbox/components/ToolboxItem.web.js new file mode 100644 index 0000000000..ac3d6067a5 --- /dev/null +++ b/react/features/toolbox/components/ToolboxItem.web.js @@ -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 { + _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 + + { this._renderIcon() } + { showLabel && this._label } + + ); + + 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 = ; + 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 ( + + { iconWrapper } + + ); + } + + return iconWrapper; + } +}