diff --git a/package-lock.json b/package-lock.json index b22e7816f..9b3ed0398 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "license": "Apache-2.0", "dependencies": { "@amplitude/react-native": "2.7.0", - "@atlaskit/dropdown-menu": "10.1.2", "@atlaskit/field-text-area": "8.0.4", "@atlaskit/flag": "14.1.0", "@atlaskit/icon": "21.2.0", @@ -372,28 +371,6 @@ "@babel/runtime": "^7.0.0" } }, - "node_modules/@atlaskit/dropdown-menu": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@atlaskit/dropdown-menu/-/dropdown-menu-10.1.2.tgz", - "integrity": "sha512-1E03eM0aoXxwzTzRamnRrBsC9OX1mU2YATfDb45RZRmz+kOx0bw1q5xGXLQsHpUPZFgfK8rdFy8h94qvrucKsQ==", - "dependencies": { - "@atlaskit/analytics-next": "^8.0.0", - "@atlaskit/button": "^15.1.0", - "@atlaskit/droplist": "^11.0.0", - "@atlaskit/icon": "^21.1.0", - "@atlaskit/item": "^12.0.0", - "@atlaskit/theme": "^11.0.0", - "@babel/runtime": "^7.0.0", - "array-find": "^1.0.0", - "prop-types": "^15.5.10", - "react-uid": "^2.2.0" - }, - "peerDependencies": { - "react": "^16.8.0", - "react-dom": "^16.8.0", - "styled-components": "^3.2.6" - } - }, "node_modules/@atlaskit/droplist": { "version": "11.0.9", "resolved": "https://registry.npmjs.org/@atlaskit/droplist/-/droplist-11.0.9.tgz", @@ -7315,11 +7292,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-find": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", - "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=" - }, "node_modules/array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -20766,23 +20738,6 @@ "@babel/runtime": "^7.0.0" } }, - "@atlaskit/dropdown-menu": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@atlaskit/dropdown-menu/-/dropdown-menu-10.1.2.tgz", - "integrity": "sha512-1E03eM0aoXxwzTzRamnRrBsC9OX1mU2YATfDb45RZRmz+kOx0bw1q5xGXLQsHpUPZFgfK8rdFy8h94qvrucKsQ==", - "requires": { - "@atlaskit/analytics-next": "^8.0.0", - "@atlaskit/button": "^15.1.0", - "@atlaskit/droplist": "^11.0.0", - "@atlaskit/icon": "^21.1.0", - "@atlaskit/item": "^12.0.0", - "@atlaskit/theme": "^11.0.0", - "@babel/runtime": "^7.0.0", - "array-find": "^1.0.0", - "prop-types": "^15.5.10", - "react-uid": "^2.2.0" - } - }, "@atlaskit/droplist": { "version": "11.0.9", "resolved": "https://registry.npmjs.org/@atlaskit/droplist/-/droplist-11.0.9.tgz", @@ -25888,11 +25843,6 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "array-find": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", - "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=" - }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", diff --git a/package.json b/package.json index e780703bb..7340adccc 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "readmeFilename": "README.md", "dependencies": { "@amplitude/react-native": "2.7.0", - "@atlaskit/dropdown-menu": "10.1.2", "@atlaskit/field-text-area": "8.0.4", "@atlaskit/flag": "14.1.0", "@atlaskit/icon": "21.2.0", diff --git a/react/features/base/dialog/components/web/AbstractDialogTab.js b/react/features/base/dialog/components/web/AbstractDialogTab.ts similarity index 85% rename from react/features/base/dialog/components/web/AbstractDialogTab.js rename to react/features/base/dialog/components/web/AbstractDialogTab.ts index 9e8ea0041..725c3d8aa 100644 --- a/react/features/base/dialog/components/web/AbstractDialogTab.js +++ b/react/features/base/dialog/components/web/AbstractDialogTab.ts @@ -1,5 +1,3 @@ -// @flow - import { Component } from 'react'; /** @@ -10,17 +8,17 @@ export type Props = { /** * Function that closes the dialog. */ - closeDialog: Function, + closeDialog: Function; /** * Callback to invoke on change. */ - onTabStateChange: Function, + onTabStateChange: Function; /** * The id of the tab. */ - tabId: number + tabId: number; }; @@ -29,7 +27,7 @@ export type Props = { * * @augments Component */ -class AbstractDialogTab extends Component { +class AbstractDialogTab

extends Component { /** * Initializes a new {@code AbstractDialogTab} instance. * @@ -43,8 +41,6 @@ class AbstractDialogTab extends Component { this._onChange = this._onChange.bind(this); } - _onChange: (Object) => void; - /** * Uses the onTabStateChange function to pass the changed state of the * controlled tab component to the controlling DialogWithTabs component. @@ -53,7 +49,7 @@ class AbstractDialogTab extends Component { * value. * @returns {void} */ - _onChange(change) { + _onChange(change: Object) { const { onTabStateChange, tabId } = this.props; onTabStateChange(tabId, { diff --git a/react/features/base/icons/svg/arrow_down.svg b/react/features/base/icons/svg/arrow_down.svg index c9265fa41..eada6f3dd 100644 --- a/react/features/base/icons/svg/arrow_down.svg +++ b/react/features/base/icons/svg/arrow_down.svg @@ -1,3 +1,3 @@ - + diff --git a/react/features/base/ui/components/web/Select.tsx b/react/features/base/ui/components/web/Select.tsx new file mode 100644 index 000000000..8fa50fa09 --- /dev/null +++ b/react/features/base/ui/components/web/Select.tsx @@ -0,0 +1,179 @@ +import { Theme } from '@mui/material'; +import React, { ChangeEvent } from 'react'; +import { makeStyles } from 'tss-react/mui'; + +import { isMobileBrowser } from '../../../environment/utils'; +import Icon from '../../../icons/components/Icon'; +import { IconArrowDown } from '../../../icons/svg'; +import { withPixelLineHeight } from '../../../styles/functions.web'; + +interface SelectProps { + + /** + * Helper text to be displayed below the select. + */ + bottomLabel?: string; + + /** + * Class name for additional styles. + */ + className?: string; + + /** + * Wether or not the select is disabled. + */ + disabled?: boolean; + + /** + * Wether or not the select is in the error state. + */ + error?: boolean; + + /** + * Label to be displayed above the select. + */ + label?: string; + + /** + * Change handler. + */ + onChange: (e: ChangeEvent) => void; + + /** + * The options of the select. + */ + options: Array<{ + label: string; + value: number | string; + }>; + + /** + * The value of the select. + */ + value: number | string; +} + +const useStyles = makeStyles()((theme: Theme) => { + return { + container: { + display: 'flex', + flexDirection: 'column' + }, + + label: { + color: theme.palette.text01, + ...withPixelLineHeight(theme.typography.bodyShortRegular), + marginBottom: theme.spacing(2), + + '&.is-mobile': { + ...withPixelLineHeight(theme.typography.bodyShortRegularLarge) + } + }, + + selectContainer: { + position: 'relative' + }, + + select: { + backgroundColor: theme.palette.ui03, + borderRadius: `${theme.shape.borderRadius}px`, + width: '100%', + ...withPixelLineHeight(theme.typography.bodyShortRegular), + color: theme.palette.text01, + padding: '8px 16px', + paddingRight: '42px', + border: 0, + appearance: 'none', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + + '&:focus': { + outline: 0, + boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}` + }, + + '&:disabled': { + color: theme.palette.text03 + }, + + '&.is-mobile': { + ...withPixelLineHeight(theme.typography.bodyShortRegularLarge), + padding: '12px 16px', + paddingRight: '46px' + }, + + '&.error': { + boxShadow: `0px 0px 0px 2px ${theme.palette.textError}` + } + }, + + icon: { + position: 'absolute', + top: '8px', + right: '8px', + pointerEvents: 'none', + + '&.is-mobile': { + top: '12px', + right: '12px' + } + }, + + bottomLabel: { + marginTop: theme.spacing(2), + ...withPixelLineHeight(theme.typography.labelRegular), + color: theme.palette.text02, + + '&.is-mobile': { + ...withPixelLineHeight(theme.typography.bodyShortRegular) + }, + + '&.error': { + color: theme.palette.textError + } + } + }; +}); + +const Select = ({ + bottomLabel, + className, + disabled, + error, + label, + onChange, + options, + value }: SelectProps) => { + const { classes, cx, theme } = useStyles(); + const isMobile = isMobileBrowser(); + + return ( +

+ {label && {label}} +
+ + +
+ {bottomLabel && ( + + {bottomLabel} + + )} +
+ ); +}; + +export default Select; diff --git a/react/features/device-selection/components/DeviceSelector.web.js b/react/features/device-selection/components/DeviceSelector.web.js index 45847132f..b4e373687 100644 --- a/react/features/device-selection/components/DeviceSelector.web.js +++ b/react/features/device-selection/components/DeviceSelector.web.js @@ -1,12 +1,8 @@ /* @flow */ - -import DropdownMenu, { - DropdownItem, - DropdownItemGroup -} from '@atlaskit/dropdown-menu'; import React, { Component } from 'react'; import { translate } from '../../base/i18n/functions'; +import Select from '../../base/ui/components/web/Select'; /** * The type of the React {@code Component} props of {@link DeviceSelector}. @@ -76,7 +72,6 @@ class DeviceSelector extends Component { super(props); this._onSelect = this._onSelect.bind(this); - this._createDropdownItem = this._createDropdownItem.bind(this); } /** @@ -98,7 +93,12 @@ class DeviceSelector extends Component { return this._renderNoDevices(); } - const items = this.props.devices.map(this._createDropdownItem); + const items = this.props.devices.map(device => { + return { + value: device.deviceId, + label: device.label || device.deviceId + }; + }); const defaultSelected = this.props.devices.find(item => item.deviceId === this.props.selectedDeviceId ); @@ -130,28 +130,6 @@ class DeviceSelector extends Component { ); } - _createDropdownItem: (Object) => void; - - /** - * Creates an object in the format expected by AKDropdownMenu for an option. - * - * @param {MediaDeviceInfo} device - An object with a label and a deviceId. - * @private - * @returns {Object} The passed in media device description converted to a - * format recognized as a valid AKDropdownMenu item. - */ - _createDropdownItem(device) { - return ( - - { device.label || device.deviceId } - - ); - } - /** * Creates a AKDropdownMenu Component using passed in props and options. If * the dropdown needs to be disabled, then only the AKDropdownMenu trigger @@ -184,18 +162,10 @@ class DeviceSelector extends Component { return (
- - - { options.items } - - + +
+ ); + } + + /** + * Callback invoked when an item has been clicked in the dropdown menu. + * + * @param {Object} e - The key event to handle. + * + * @returns {void} + */ + _onSelect(e: React.ChangeEvent) { + const streamId = e.target.value; + + this.props.onBroadcastSelected(streamId); + } +} + +export default translate(StreamKeyPicker); diff --git a/react/features/settings/components/web/ModeratorTab.tsx b/react/features/settings/components/web/ModeratorTab.tsx index 2c7936ddd..63e403781 100644 --- a/react/features/settings/components/web/ModeratorTab.tsx +++ b/react/features/settings/components/web/ModeratorTab.tsx @@ -10,7 +10,7 @@ import { translate } from '../../../base/i18n/functions'; import Checkbox from '../../../base/ui/components/web/Checkbox'; /** - * The type of the React {@code Component} props of {@link MoreTab}. + * The type of the React {@code Component} props of {@link ModeratorTab}. */ export type Props = AbstractDialogTabProps & WithTranslation & { @@ -60,7 +60,7 @@ export type Props = AbstractDialogTabProps & WithTranslation & { */ class ModeratorTab extends AbstractDialogTab { /** - * Initializes a new {@code MoreTab} instance. + * Initializes a new {@code ModeratorTab} instance. * * @param {Object} props - The read-only properties with which the new * instance is to be initialized. diff --git a/react/features/settings/components/web/MoreTab.tsx b/react/features/settings/components/web/MoreTab.tsx index f308cffb3..961a0df22 100644 --- a/react/features/settings/components/web/MoreTab.tsx +++ b/react/features/settings/components/web/MoreTab.tsx @@ -1,23 +1,16 @@ /* eslint-disable lines-around-comment */ -import DropdownMenu, { - DropdownItem, - DropdownItemGroup -} from '@atlaskit/dropdown-menu'; import React from 'react'; import { WithTranslation } from 'react-i18next'; // @ts-ignore import keyboardShortcut from '../../../../../modules/keyboardshortcut/keyboardshortcut'; -// @ts-ignore -import { AbstractDialogTab } from '../../../base/dialog'; -// @ts-ignore -import type { Props as AbstractDialogTabProps } from '../../../base/dialog'; +import AbstractDialogTab, { + Props as AbstractDialogTabProps +} from '../../../base/dialog/components/web/AbstractDialogTab'; import { translate } from '../../../base/i18n/functions'; import Checkbox from '../../../base/ui/components/web/Checkbox'; -// @ts-ignore -import TouchmoveHack from '../../../chat/components/web/TouchmoveHack'; +import Select from '../../../base/ui/components/web/Select'; import { MAX_ACTIVE_PARTICIPANTS } from '../../../filmstrip/constants'; -// @ts-ignore import { SS_DEFAULT_FRAME_RATE } from '../../constants'; /** @@ -66,6 +59,11 @@ export type Props = AbstractDialogTabProps & WithTranslation & { */ languages: Array; + /** + * The number of max participants to display on stage. + */ + maxStageParticipants: number; + /** * Whether or not to display the language select dropdown. */ @@ -91,34 +89,23 @@ export type Props = AbstractDialogTabProps & WithTranslation & { */ showPrejoinSettings: boolean; + /** + * Wether or not the stage filmstrip is enabled. + */ + stageFilmstripEnabled: boolean; + /** * Invoked to obtain translated strings. */ t: Function; }; -/** - * The type of the React {@code Component} state of {@link MoreTab}. - */ -type State = { - - /** - * Whether or not the desktop share frame rate select dropdown is open. - */ - isFramerateSelectOpen: boolean; - - /** - * Whether or not the language select dropdown is open. - */ - isLanguageSelectOpen: boolean; -}; - /** * React {@code Component} for modifying language and moderator settings. * * @augments Component */ -class MoreTab extends AbstractDialogTab { +class MoreTab extends AbstractDialogTab { /** * Initializes a new {@code MoreTab} instance. * @@ -128,17 +115,8 @@ class MoreTab extends AbstractDialogTab { constructor(props: Props) { super(props); - // @ts-ignore - this.state = { - isFramerateSelectOpen: false, - isLanguageSelectOpen: false, - isMaxStageParticipantsOpen: false - }; - // Bind event handler so it is only bound once for every instance. - this._onFramerateDropdownOpenChange = this._onFramerateDropdownOpenChange.bind(this); this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this); - this._onLanguageDropdownOpenChange = this._onLanguageDropdownOpenChange.bind(this); this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this); this._onEnabledNotificationsChanged = this._onEnabledNotificationsChanged.bind(this); this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this); @@ -146,7 +124,6 @@ class MoreTab extends AbstractDialogTab { this._onHideSelfViewChanged = this._onHideSelfViewChanged.bind(this); this._renderMaxStageParticipantsSelect = this._renderMaxStageParticipantsSelect.bind(this); this._onMaxStageParticipantsSelect = this._onMaxStageParticipantsSelect.bind(this); - this._onMaxStageParticipantsOpenChange = this._onMaxStageParticipantsOpenChange.bind(this); } /** @@ -170,18 +147,6 @@ class MoreTab extends AbstractDialogTab { ); } - /** - * Callback invoked to toggle display of the desktop share framerate select dropdown. - * - * @param {Object} event - The event for opening or closing the dropdown. - * @private - * @returns {void} - */ - _onFramerateDropdownOpenChange({ isOpen }: { isOpen: boolean; }) { - // @ts-ignore - this.setState({ isFramerateSelectOpen: isOpen }); - } - /** * Callback invoked to select a frame rate from the select dropdown. * @@ -190,23 +155,11 @@ class MoreTab extends AbstractDialogTab { * @returns {void} */ _onFramerateItemSelect(e: React.ChangeEvent) { - const frameRate = e.currentTarget.getAttribute('data-framerate'); + const frameRate = e.target.value; super._onChange({ currentFramerate: frameRate }); } - /** - * Callback invoked to toggle display of the language select dropdown. - * - * @param {Object} event - The event for opening or closing the dropdown. - * @private - * @returns {void} - */ - _onLanguageDropdownOpenChange({ isOpen }: { isOpen: boolean; }) { - // @ts-ignore - this.setState({ isLanguageSelectOpen: isOpen }); - } - /** * Callback invoked to select a language from select dropdown. * @@ -215,7 +168,7 @@ class MoreTab extends AbstractDialogTab { * @returns {void} */ _onLanguageItemSelect(e: React.ChangeEvent) { - const language = e.currentTarget.getAttribute('data-language'); + const language = e.target.value; super._onChange({ currentLanguage: language }); } @@ -244,7 +197,6 @@ class MoreTab extends AbstractDialogTab { _onEnabledNotificationsChanged({ target: { checked } }: React.ChangeEvent, type: any) { super._onChange({ enabledNotifications: { - // @ts-ignore ...this.props.enabledNotifications, [type]: checked } @@ -275,18 +227,6 @@ class MoreTab extends AbstractDialogTab { super._onChange({ hideSelfView: checked }); } - /** - * Callback invoked to toggle display of the max stage participants select dropdown. - * - * @param {Object} event - The event for opening or closing the dropdown. - * @private - * @returns {void} - */ - _onMaxStageParticipantsOpenChange({ isOpen }: { isOpen: boolean; }) { - // @ts-ignore - this.setState({ isMaxStageParticipantsOpen: isOpen }); - } - /** * Callback invoked to select a max number of stage participants from the select dropdown. * @@ -306,50 +246,27 @@ class MoreTab extends AbstractDialogTab { * @returns {ReactElement} */ _renderFramerateSelect() { - // @ts-ignore const { currentFramerate, desktopShareFramerates, t } = this.props; - const frameRateItems = desktopShareFramerates.map((frameRate: string) => ( - - { `${frameRate} ${t('settings.framesPerSecond')}` } - )); + const frameRateItems = desktopShareFramerates.map((frameRate: number) => { + return { + value: frameRate, + label: `${frameRate} ${t('settings.framesPerSecond')}` + }; + }); return (
-

- { t('settings.desktopShareFramerate') } -

- - - - { frameRateItems } - - - -
-
- { parseInt(currentFramerate, 10) > SS_DEFAULT_FRAME_RATE - ? t('settings.desktopShareHighFpsWarning') - : t('settings.desktopShareWarning') } +
); @@ -470,16 +365,15 @@ class MoreTab extends AbstractDialogTab { * @returns {ReactElement} */ _renderPrejoinScreenSettings() { - // @ts-ignore const { t, showPrejoinPage } = this.props; return (
-

+ { t('prejoin.premeeting') } -

+ { * @returns {ReactElement} */ _renderNotificationsSettings() { - // @ts-ignore const { t, enabledNotifications } = this.props; return (
-

+ { t('notify.displayNotifications') } -

+ { Object.keys(enabledNotifications).map(key => ( { * @returns {ReactElement} */ _renderMaxStageParticipantsSelect() { - // @ts-ignore const { maxStageParticipants, t, stageFilmstripEnabled } = this.props; if (!stageFilmstripEnabled) { return null; } const maxParticipantsItems = Array(MAX_ACTIVE_PARTICIPANTS).fill(0) - .map((no, index) => ( - - {index + 1} - )); + .map((no, index) => { + return { + value: index + 1, + label: `${index + 1}` + }; + }); return (
-

- { t('settings.maxStageParticipants') } -

- - - - { maxParticipantsItems } - - - +