Parcourir la 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é il y a 7 ans
Parent
révision
8d94cc5cb2

+ 198
- 0
react/features/toolbox/components/AbstractToolboxItem.js Voir le fichier

@@ -0,0 +1,198 @@
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 Voir le fichier

@@ -127,30 +127,23 @@ class Toolbox extends Component<Props> {
127 127
      * button to get styles for.
128 128
      * @protected
129 129
      * @returns {{
130
-     *     iconName: string,
131 130
      *     iconStyle: Object,
132 131
      *     style: Object
133 132
      * }}
134 133
      */
135 134
     _getMuteButtonStyles(mediaType) {
136
-        let iconName;
137 135
         let iconStyle;
138 136
         let style;
139 137
 
140 138
         if (this.props[`_${mediaType}Muted`]) {
141
-            iconName = `${mediaType}MutedIcon`;
142 139
             iconStyle = styles.whitePrimaryToolbarButtonIcon;
143 140
             style = styles.whitePrimaryToolbarButton;
144 141
         } else {
145
-            iconName = `${mediaType}Icon`;
146 142
             iconStyle = styles.primaryToolbarButtonIcon;
147 143
             style = styles.primaryToolbarButton;
148 144
         }
149 145
 
150 146
         return {
151
-
152
-            // $FlowExpectedError
153
-            iconName: this[iconName],
154 147
             iconStyle,
155 148
             style
156 149
         };
@@ -174,9 +167,9 @@ class Toolbox extends Component<Props> {
174 167
                 key = 'primaryToolbar'
175 168
                 pointerEvents = 'box-none'
176 169
                 style = { styles.primaryToolbar }>
177
-                <AudioMuteButton buttonStyles = { audioButtonStyles } />
170
+                <AudioMuteButton styles = { audioButtonStyles } />
178 171
                 <HangupButton />
179
-                <VideoMuteButton buttonStyles = { videoButtonStyles } />
172
+                <VideoMuteButton styles = { videoButtonStyles } />
180 173
             </View>
181 174
         );
182 175
 
@@ -263,20 +256,6 @@ class Toolbox extends Component<Props> {
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 260
  * Maps redux actions to {@link Toolbox}'s React {@code Component} props.
282 261
  *

+ 57
- 0
react/features/toolbox/components/ToolboxItem.native.js Voir le fichier

@@ -0,0 +1,57 @@
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 Voir le fichier

@@ -0,0 +1,79 @@
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
+}

Chargement…
Annuler
Enregistrer