Browse Source

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.
master
Saúl Ibarra Corretgé 7 years ago
parent
commit
8d94cc5cb2

+ 198
- 0
react/features/toolbox/components/AbstractToolboxItem.js View File

1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+export type Styles = {
6
+
7
+    /**
8
+     * Style for the item's icon.
9
+     */
10
+    iconStyle: Object,
11
+
12
+    /**
13
+     * Style for the item itself.
14
+     */
15
+    style: Object,
16
+
17
+    /**
18
+     * Color for the item underlay (shows when clicked).
19
+     */
20
+    underlayColor: string
21
+};
22
+
23
+export type Props = {
24
+
25
+    /**
26
+     * A succinct description of what the item does. Used by accessibility
27
+     * tools and torture tests.
28
+     */
29
+    accessibilityLabel: string,
30
+
31
+    /**
32
+     * Whether this item is disabled or not. When disabled, clicking an the item
33
+     * has no effect, and it may reflect on its style.
34
+     */
35
+    disabled: boolean,
36
+
37
+    /**
38
+     * The name of the icon of this {@code ToolboxItem}.
39
+     */
40
+    iconName: string,
41
+
42
+    /**
43
+     * The text associated with this item. When `showLabel` is set to
44
+     * {@code true}, it will be displayed alongside the icon.
45
+     */
46
+    label: string,
47
+
48
+    /**
49
+     * On click handler.
50
+     */
51
+    onClick: Function,
52
+
53
+    /**
54
+     * Whether to show the label or not.
55
+     */
56
+    showLabel: boolean,
57
+
58
+    /**
59
+     * Collection of styles for the item. Used only on native.
60
+     */
61
+    styles: ?Styles,
62
+
63
+    /**
64
+     * Invoked to obtain translated strings.
65
+     */
66
+    t: ?Function,
67
+
68
+    /**
69
+     * The text to display in the tooltip. Used only on web.
70
+     */
71
+    tooltip: string,
72
+
73
+    /**
74
+     * From which direction the tooltip should appear, relative to the
75
+     * item. Used only on web.
76
+     */
77
+    tooltipPosition: string,
78
+
79
+    /**
80
+     * Whether this item is visible or not.
81
+     */
82
+    visible: boolean
83
+};
84
+
85
+/**
86
+ * Abstract (base) class for an item in {@link Toolbox}. The item can be located
87
+ * anywhere in the {@link Toolbox}, it will morph its shape to accommodate it.
88
+ *
89
+ * @abstract
90
+ */
91
+export default class AbstractToolboxItem<P : Props> extends Component<P> {
92
+    /**
93
+     * Default values for {@code AbstractToolboxItem} component's properties.
94
+     *
95
+     * @static
96
+     */
97
+    static defaultProps = {
98
+        disabled: false,
99
+        label: '',
100
+        showLabel: false,
101
+        t: undefined,
102
+        tooltip: '',
103
+        tooltipPosition: 'top',
104
+        visible: true
105
+    };
106
+
107
+    /**
108
+     * Initializes a new {@code AbstractToolboxItem} instance.
109
+     *
110
+     * @param {Object} props - The React {@code Component} props to initialize
111
+     * the new {@code AbstractToolboxItem} instance with.
112
+     */
113
+    constructor(props: P) {
114
+        super(props);
115
+
116
+        // Bind event handlers so they are only bound once per instance.
117
+        this._onClick = this._onClick.bind(this);
118
+    }
119
+
120
+    /**
121
+     * Helper property to get the item label. If a translation function was
122
+     * provided then it will be translated using it.
123
+     *
124
+     * @protected
125
+     * @returns {string}
126
+     */
127
+    get _label() {
128
+        return this._maybeTranslateAttribute(this.props.label);
129
+    }
130
+
131
+    /**
132
+     * Helper property to get the item tooltip. If a translation function was
133
+     * provided then it will be translated using it.
134
+     *
135
+     * @protected
136
+     * @returns {string}
137
+     */
138
+    get _tooltip() {
139
+        return this._maybeTranslateAttribute(this.props.tooltip);
140
+    }
141
+
142
+    /**
143
+     * Utility function to translate the given string, if a translation
144
+     * function is available.
145
+     *
146
+     * @param {string} text - What needs translating.
147
+     * @private
148
+     * @returns {string}
149
+     */
150
+    _maybeTranslateAttribute(text) {
151
+        const { t } = this.props;
152
+
153
+        if (typeof t === 'function') {
154
+            return t(text);
155
+        }
156
+
157
+        return text;
158
+    }
159
+
160
+    _onClick: (*) => void;
161
+
162
+    /**
163
+     * Handles clicking/pressing this {@code AbstractToolboxItem} by
164
+     * forwarding the event to the {@code onClick} prop of this instance if any.
165
+     *
166
+     * @protected
167
+     * @returns {void}
168
+     */
169
+    _onClick(...args) {
170
+        const { disabled, onClick } = this.props;
171
+
172
+        !disabled && onClick && onClick(...args);
173
+    }
174
+
175
+    /**
176
+     * Handles rendering of the actual item.
177
+     *
178
+     * @protected
179
+     * @returns {ReactElement}
180
+     */
181
+    _renderItem() {
182
+        // To be implemented by a subclass.
183
+    }
184
+
185
+    /**
186
+     * Implements React's {@link Component#render()}.
187
+     *
188
+     * @inheritdoc
189
+     * @returns {ReactElement}
190
+     */
191
+    render() {
192
+        if (!this.props.visible) {
193
+            return null;
194
+        }
195
+
196
+        return this._renderItem();
197
+    }
198
+}

+ 2
- 23
react/features/toolbox/components/Toolbox.native.js View File

127
      * button to get styles for.
127
      * button to get styles for.
128
      * @protected
128
      * @protected
129
      * @returns {{
129
      * @returns {{
130
-     *     iconName: string,
131
      *     iconStyle: Object,
130
      *     iconStyle: Object,
132
      *     style: Object
131
      *     style: Object
133
      * }}
132
      * }}
134
      */
133
      */
135
     _getMuteButtonStyles(mediaType) {
134
     _getMuteButtonStyles(mediaType) {
136
-        let iconName;
137
         let iconStyle;
135
         let iconStyle;
138
         let style;
136
         let style;
139
 
137
 
140
         if (this.props[`_${mediaType}Muted`]) {
138
         if (this.props[`_${mediaType}Muted`]) {
141
-            iconName = `${mediaType}MutedIcon`;
142
             iconStyle = styles.whitePrimaryToolbarButtonIcon;
139
             iconStyle = styles.whitePrimaryToolbarButtonIcon;
143
             style = styles.whitePrimaryToolbarButton;
140
             style = styles.whitePrimaryToolbarButton;
144
         } else {
141
         } else {
145
-            iconName = `${mediaType}Icon`;
146
             iconStyle = styles.primaryToolbarButtonIcon;
142
             iconStyle = styles.primaryToolbarButtonIcon;
147
             style = styles.primaryToolbarButton;
143
             style = styles.primaryToolbarButton;
148
         }
144
         }
149
 
145
 
150
         return {
146
         return {
151
-
152
-            // $FlowExpectedError
153
-            iconName: this[iconName],
154
             iconStyle,
147
             iconStyle,
155
             style
148
             style
156
         };
149
         };
174
                 key = 'primaryToolbar'
167
                 key = 'primaryToolbar'
175
                 pointerEvents = 'box-none'
168
                 pointerEvents = 'box-none'
176
                 style = { styles.primaryToolbar }>
169
                 style = { styles.primaryToolbar }>
177
-                <AudioMuteButton buttonStyles = { audioButtonStyles } />
170
+                <AudioMuteButton styles = { audioButtonStyles } />
178
                 <HangupButton />
171
                 <HangupButton />
179
-                <VideoMuteButton buttonStyles = { videoButtonStyles } />
172
+                <VideoMuteButton styles = { videoButtonStyles } />
180
             </View>
173
             </View>
181
         );
174
         );
182
 
175
 
263
     }
256
     }
264
 }
257
 }
265
 
258
 
266
-/**
267
- * Additional properties for various icons, which are now platform-dependent.
268
- * This is done to have common logic of generating styles for web and native.
269
- * TODO As soon as we have common font sets for web and native, this will no
270
- * longer be required.
271
- */
272
-// $FlowExpectedError
273
-Object.assign(Toolbox.prototype, {
274
-    audioIcon: 'microphone',
275
-    audioMutedIcon: 'mic-disabled',
276
-    videoIcon: 'camera',
277
-    videoMutedIcon: 'camera-disabled'
278
-});
279
-
280
 /**
259
 /**
281
  * Maps redux actions to {@link Toolbox}'s React {@code Component} props.
260
  * Maps redux actions to {@link Toolbox}'s React {@code Component} props.
282
  *
261
  *

+ 57
- 0
react/features/toolbox/components/ToolboxItem.native.js View File

1
+// @flow
2
+
3
+import React from 'react';
4
+import { TouchableHighlight } from 'react-native';
5
+
6
+import { Icon } from '../../base/font-icons';
7
+
8
+import AbstractToolboxItem from './AbstractToolboxItem';
9
+import type { Props } from './AbstractToolboxItem';
10
+
11
+/**
12
+ * Native implementation of {@code AbstractToolboxItem}.
13
+ */
14
+export default class ToolboxItem extends AbstractToolboxItem<Props> {
15
+    /**
16
+     * Transform the given (web) icon name into a name that works with
17
+     * {@code Icon}.
18
+     *
19
+     * @private
20
+     * @returns {string}
21
+     */
22
+    _getIconName() {
23
+        const { iconName } = this.props;
24
+
25
+        return iconName.replace('icon-', '').split(' ')[0];
26
+    }
27
+
28
+    /**
29
+     * Handles rendering of the actual item.
30
+     *
31
+     * TODO: currently no handling for labels is implemented.
32
+     *
33
+     * @protected
34
+     * @returns {ReactElement}
35
+     */
36
+    _renderItem() {
37
+        const {
38
+            accessibilityLabel,
39
+            disabled,
40
+            onClick,
41
+            styles
42
+        } = this.props;
43
+
44
+        return (
45
+            <TouchableHighlight
46
+                accessibilityLabel = { accessibilityLabel }
47
+                disabled = { disabled }
48
+                onPress = { onClick }
49
+                style = { styles && styles.style }
50
+                underlayColor = { styles && styles.underlayColor } >
51
+                <Icon
52
+                    name = { this._getIconName() }
53
+                    style = { styles && styles.iconStyle } />
54
+            </TouchableHighlight>
55
+        );
56
+    }
57
+}

+ 79
- 0
react/features/toolbox/components/ToolboxItem.web.js View File

1
+// @flow
2
+
3
+import Tooltip from '@atlaskit/tooltip';
4
+import React from 'react';
5
+
6
+import AbstractToolboxItem from './AbstractToolboxItem';
7
+import type { Props } from './AbstractToolboxItem';
8
+
9
+/**
10
+ * Web implementation of {@code AbstractToolboxItem}.
11
+ */
12
+export default class ToolboxItem extends AbstractToolboxItem<Props> {
13
+    _label: string;
14
+    _tooltip: string;
15
+
16
+    /**
17
+     * Handles rendering of the actual item. If the label is being shown, which
18
+     * is controlled with the `showLabel` prop, the item is rendered for its
19
+     * display in an overflow menu, otherwise it will only have an icon, which
20
+     * can be displayed on any toolbar.
21
+     *
22
+     * @protected
23
+     * @returns {ReactElement}
24
+     */
25
+    _renderItem() {
26
+        const {
27
+            accessibilityLabel,
28
+            onClick,
29
+            showLabel
30
+        } = this.props;
31
+        const props = {
32
+            'aria-label': accessibilityLabel,
33
+            className: showLabel ? 'overflow-menu-item' : 'toolbox-button',
34
+            onClick
35
+        };
36
+        const elementType = showLabel ? 'li' : 'div';
37
+        // eslint-disable-next-line no-extra-parens
38
+        const children = (
39
+
40
+            // $FlowFixMe
41
+            <React.Fragment>
42
+                { this._renderIcon() }
43
+                { showLabel && this._label }
44
+            </React.Fragment>
45
+        );
46
+
47
+        return React.createElement(elementType, props, children);
48
+    }
49
+
50
+    /**
51
+     * Helper function to render the item's icon.
52
+     *
53
+     * @private
54
+     * @returns {ReactElement}
55
+     */
56
+    _renderIcon() {
57
+        const { iconName, tooltipPosition, showLabel } = this.props;
58
+        const icon = <i className = { iconName } />;
59
+        const elementType = showLabel ? 'span' : 'div';
60
+        const className
61
+            = showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon';
62
+        const iconWrapper
63
+            = React.createElement(elementType, { className }, icon);
64
+        const tooltip = this._tooltip;
65
+        const useTooltip = !showLabel && tooltip && tooltip.length > 0;
66
+
67
+        if (useTooltip) {
68
+            return (
69
+                <Tooltip
70
+                    description = { tooltip }
71
+                    position = { tooltipPosition }>
72
+                    { iconWrapper }
73
+                </Tooltip>
74
+            );
75
+        }
76
+
77
+        return iconWrapper;
78
+    }
79
+}

Loading…
Cancel
Save