Merge pull request #12474 from Tainan404/refactor-dropdown

refactor: Decrease amount of imports in dropdown
This commit is contained in:
Anton Georgiev 2021-06-01 09:32:14 -04:00 committed by GitHub
commit 9395f50b99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 196 additions and 486 deletions

View File

@ -4,13 +4,8 @@ import PropTypes from 'prop-types';
import { defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import Button from '/imports/ui/components/button/component'; import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import { withModalMounter } from '/imports/ui/components/modal/service'; import { withModalMounter } from '/imports/ui/components/modal/service';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import ExternalVideoModal from '/imports/ui/components/external-video-player/modal/container'; import ExternalVideoModal from '/imports/ui/components/external-video-player/modal/container';
import RandomUserSelectContainer from '/imports/ui/components/modal/random-user/container'; import RandomUserSelectContainer from '/imports/ui/components/modal/random-user/container';
import cx from 'classnames'; import cx from 'classnames';
@ -145,7 +140,7 @@ class ActionsDropdown extends PureComponent {
return _.compact([ return _.compact([
(amIPresenter && isPollingEnabled (amIPresenter && isPollingEnabled
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
icon="polling" icon="polling"
data-test="polling" data-test="polling"
label={formatMessage(pollBtnLabel)} label={formatMessage(pollBtnLabel)}
@ -170,7 +165,7 @@ class ActionsDropdown extends PureComponent {
: null), : null),
(!amIPresenter (!amIPresenter
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
icon="presentation" icon="presentation"
label={formatMessage(takePresenter)} label={formatMessage(takePresenter)}
description={formatMessage(takePresenterDesc)} description={formatMessage(takePresenterDesc)}
@ -181,7 +176,7 @@ class ActionsDropdown extends PureComponent {
: null), : null),
(amIPresenter (amIPresenter
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
data-test="uploadPresentation" data-test="uploadPresentation"
icon="presentation" icon="presentation"
label={formatMessage(presentationLabel)} label={formatMessage(presentationLabel)}
@ -193,7 +188,7 @@ class ActionsDropdown extends PureComponent {
: null), : null),
(amIPresenter && allowExternalVideo (amIPresenter && allowExternalVideo
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
icon="video" icon="video"
label={!isSharingVideo ? intl.formatMessage(intlMessages.startExternalVideoLabel) label={!isSharingVideo ? intl.formatMessage(intlMessages.startExternalVideoLabel)
: intl.formatMessage(intlMessages.stopExternalVideoLabel)} : intl.formatMessage(intlMessages.stopExternalVideoLabel)}
@ -205,7 +200,7 @@ class ActionsDropdown extends PureComponent {
: null), : null),
(amIPresenter && isSelectRandomUserEnabled (amIPresenter && isSelectRandomUserEnabled
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
icon="user" icon="user"
label={intl.formatMessage(intlMessages.selectRandUserLabel)} label={intl.formatMessage(intlMessages.selectRandUserLabel)}
description={intl.formatMessage(intlMessages.selectRandUserDesc)} description={intl.formatMessage(intlMessages.selectRandUserDesc)}
@ -238,7 +233,7 @@ class ActionsDropdown extends PureComponent {
itemStyles[styles.isCurrent] = p.current; itemStyles[styles.isCurrent] = p.current;
return ( return (
<DropdownListItem <Dropdown.DropdownListItem
className={cx(itemStyles)} className={cx(itemStyles)}
icon="file" icon="file"
iconRight={p.current ? 'check' : null} iconRight={p.current ? 'check' : null}
@ -252,7 +247,7 @@ class ActionsDropdown extends PureComponent {
); );
}); });
presentationItemElements.push(<DropdownListSeparator key={_.uniqueId('list-separator-')} />); presentationItemElements.push(<Dropdown.DropdownListSeparator key={_.uniqueId('list-separator-')} />);
return presentationItemElements; return presentationItemElements;
} }
@ -288,7 +283,7 @@ class ActionsDropdown extends PureComponent {
className={styles.dropdown} className={styles.dropdown}
ref={(ref) => { this._dropdown = ref; }} ref={(ref) => { this._dropdown = ref; }}
> >
<DropdownTrigger tabIndex={0} accessKey={OPEN_ACTIONS_AK}> <Dropdown.DropdownTrigger tabIndex={0} accessKey={OPEN_ACTIONS_AK}>
<Button <Button
className={isDropdownOpen ? styles.hideDropdownButton : ''} className={isDropdownOpen ? styles.hideDropdownButton : ''}
hideLabel hideLabel
@ -300,12 +295,12 @@ class ActionsDropdown extends PureComponent {
circle circle
onClick={() => null} onClick={() => null}
/> />
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent placement="top left"> <Dropdown.DropdownContent placement="top left">
<DropdownList className={styles.scrollableList}> <Dropdown.DropdownList className={styles.scrollableList}>
{children} {children}
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -4,10 +4,6 @@ import { defineMessages } from 'react-intl';
import _ from 'lodash'; import _ from 'lodash';
import Button from '/imports/ui/components/button/component'; import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import { styles } from '../styles'; import { styles } from '../styles';
import { PANELS, ACTIONS } from '../../layout/enums'; import { PANELS, ACTIONS } from '../../layout/enums';
@ -111,7 +107,7 @@ const getAvailableQuickPolls = (
}).join(''); }).join('');
return ( return (
<DropdownListItem <Dropdown.DropdownListItem
label={itemLabel} label={itemLabel}
key={_.uniqueId('quick-poll-item')} key={_.uniqueId('quick-poll-item')}
onClick={() => { onClick={() => {
@ -210,14 +206,14 @@ class QuickPollDropdown extends Component {
dropdown = ( dropdown = (
<Dropdown className={className}> <Dropdown className={className}>
<DropdownTrigger tabIndex={0}> <Dropdown.DropdownTrigger tabIndex={0}>
{btn} {btn}
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent placement="top left"> <Dropdown.DropdownContent placement="top left">
<DropdownList> <Dropdown.DropdownList>
{quickPolls} {quickPolls}
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -5,12 +5,6 @@ import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Button from '/imports/ui/components/button/component'; import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import cx from 'classnames'; import cx from 'classnames';
@ -222,14 +216,14 @@ class InputStreamLiveSelector extends Component {
const listLenght = list ? list.length : -1; const listLenght = list ? list.length : -1;
const listTitle = [ const listTitle = [
<DropdownListTitle key={`audioDeviceList-${deviceKind}`}> <Dropdown.DropdownListTitle key={`audioDeviceList-${deviceKind}`}>
{title} {title}
</DropdownListTitle>, </Dropdown.DropdownListTitle>,
]; ];
const deviceList = (listLenght > 0) const deviceList = (listLenght > 0)
? list.map((device) => ( ? list.map((device) => (
<DropdownListItem <Dropdown.DropdownListItem
key={`${device.deviceId}-${deviceKind}`} key={`${device.deviceId}-${deviceKind}`}
label={InputStreamLiveSelector.truncateDeviceName(device.label)} label={InputStreamLiveSelector.truncateDeviceName(device.label)}
onClick={() => this.onDeviceListClick(device.deviceId, deviceKind, onClick={() => this.onDeviceListClick(device.deviceId, deviceKind,
@ -239,7 +233,7 @@ class InputStreamLiveSelector extends Component {
/> />
)) ))
: [ : [
<DropdownListItem <Dropdown.DropdownListItem
key={`noDeviceFoundKey-${deviceKind}-`} key={`noDeviceFoundKey-${deviceKind}-`}
className={styles.disableDeviceSelection} className={styles.disableDeviceSelection}
label={ label={
@ -251,7 +245,7 @@ class InputStreamLiveSelector extends Component {
]; ];
const listSeparator = [ const listSeparator = [
<DropdownListSeparator key={`audioDeviceListSeparator-${deviceKind}`} />, <Dropdown.DropdownListSeparator key={`audioDeviceListSeparator-${deviceKind}`} />,
]; ];
return listTitle.concat(deviceList).concat(listSeparator); return listTitle.concat(deviceList).concat(listSeparator);
@ -295,7 +289,7 @@ class InputStreamLiveSelector extends Component {
const dropdownListComplete = inputDeviceList.concat(outputDeviceList) const dropdownListComplete = inputDeviceList.concat(outputDeviceList)
.concat([ .concat([
<DropdownListItem <Dropdown.DropdownListItem
key="leaveAudioButtonKey" key="leaveAudioButtonKey"
className={styles.stopButton} className={styles.stopButton}
label={intl.formatMessage(intlMessages.leaveAudio)} label={intl.formatMessage(intlMessages.leaveAudio)}
@ -306,7 +300,7 @@ class InputStreamLiveSelector extends Component {
return ( return (
<Dropdown> <Dropdown>
<DropdownTrigger> <Dropdown.DropdownTrigger>
<Button <Button
aria-label={intl.formatMessage(intlMessages.changeLeaveAudio)} aria-label={intl.formatMessage(intlMessages.changeLeaveAudio)}
label={intl.formatMessage(intlMessages.changeLeaveAudio)} label={intl.formatMessage(intlMessages.changeLeaveAudio)}
@ -317,12 +311,14 @@ class InputStreamLiveSelector extends Component {
circle circle
onClick={() => {}} onClick={() => {}}
/> />
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent className={styles.dropdownContent}> <Dropdown.DropdownContent className={styles.dropdownContent}>
<DropdownList className={cx(styles.scrollableList, styles.dropdownListContainer)}> <Dropdown.DropdownList
className={cx(styles.scrollableList, styles.dropdownListContainer)}
>
{dropdownListComplete} {dropdownListComplete}
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -4,10 +4,6 @@ import { withModalMounter } from '/imports/ui/components/modal/service';
import Clipboard from 'clipboard'; import Clipboard from 'clipboard';
import _ from 'lodash'; import _ from 'lodash';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import Button from '/imports/ui/components/button/component'; import Button from '/imports/ui/components/button/component';
import ChatService from '../service'; import ChatService from '../service';
@ -89,7 +85,7 @@ class ChatDropdown extends PureComponent {
const saveIcon = 'download'; const saveIcon = 'download';
const copyIcon = 'copy'; const copyIcon = 'copy';
return _.compact([ return _.compact([
<DropdownListItem <Dropdown.DropdownListItem
data-test="chatSave" data-test="chatSave"
icon={saveIcon} icon={saveIcon}
label={intl.formatMessage(intlMessages.save)} label={intl.formatMessage(intlMessages.save)}
@ -109,7 +105,7 @@ class ChatDropdown extends PureComponent {
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
}} }}
/>, />,
<DropdownListItem <Dropdown.DropdownListItem
data-test="chatCopy" data-test="chatCopy"
icon={copyIcon} icon={copyIcon}
id="clipboardButton" id="clipboardButton"
@ -117,7 +113,7 @@ class ChatDropdown extends PureComponent {
key={this.actionsKey[1]} key={this.actionsKey[1]}
/>, />,
!meetingIsBreakout && amIModerator && isMeteorConnected ? ( !meetingIsBreakout && amIModerator && isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
data-test="chatClear" data-test="chatClear"
icon={clearIcon} icon={clearIcon}
label={intl.formatMessage(intlMessages.clear)} label={intl.formatMessage(intlMessages.clear)}
@ -140,7 +136,7 @@ class ChatDropdown extends PureComponent {
onShow={this.onActionsShow} onShow={this.onActionsShow}
onHide={this.onActionsHide} onHide={this.onActionsHide}
> >
<DropdownTrigger tabIndex={0}> <Dropdown.DropdownTrigger tabIndex={0}>
<Button <Button
data-test="chatDropdownTrigger" data-test="chatDropdownTrigger"
icon="more" icon="more"
@ -153,10 +149,10 @@ class ChatDropdown extends PureComponent {
aria-label={intl.formatMessage(intlMessages.options)} aria-label={intl.formatMessage(intlMessages.options)}
onClick={() => null} onClick={() => null}
/> />
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent placement="bottom right"> <Dropdown.DropdownContent placement="bottom right">
<DropdownList>{availableActions}</DropdownList> <Dropdown.DropdownList>{availableActions}</Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -9,8 +9,13 @@ import Button from '/imports/ui/components/button/component';
import screenreaderTrap from 'makeup-screenreader-trap'; import screenreaderTrap from 'makeup-screenreader-trap';
import { Session } from 'meteor/session'; import { Session } from 'meteor/session';
import { styles } from './styles'; import { styles } from './styles';
import DropdownTrigger from './trigger/component';
import DropdownContent from './content/component'; import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
const intlMessages = defineMessages({ const intlMessages = defineMessages({
close: { close: {
@ -33,8 +38,8 @@ const propTypes = {
+ ` \`${componentName}\`. Validation failed.`); + ` \`${componentName}\`. Validation failed.`);
} }
const trigger = children.find(x => x.type === DropdownTrigger); const trigger = children.find((x) => x.type === DropdownTrigger);
const content = children.find(x => x.type === DropdownContent); const content = children.find((x) => x.type === DropdownContent);
if (!trigger) { if (!trigger) {
return new Error(`Invalid prop \`${propName}\` supplied to` return new Error(`Invalid prop \`${propName}\` supplied to`
@ -53,11 +58,12 @@ const propTypes = {
onHide: PropTypes.func, onHide: PropTypes.func,
onShow: PropTypes.func, onShow: PropTypes.func,
autoFocus: PropTypes.bool, autoFocus: PropTypes.bool,
intl: PropTypes.object.isRequired,
tethered: PropTypes.bool, tethered: PropTypes.bool,
getContent: PropTypes.func,
}; };
const defaultProps = { const defaultProps = {
tethered: false,
children: null, children: null,
onShow: noop, onShow: noop,
onHide: noop, onHide: noop,
@ -79,11 +85,19 @@ const targetAttachments = {
class Dropdown extends Component { class Dropdown extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { isOpen: false, isPortrait:deviceInfo.isPortrait() }; this.state = {
isOpen: false,
isPortrait: deviceInfo.isPortrait(),
};
this.handleShow = this.handleShow.bind(this); this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this); this.handleHide = this.handleHide.bind(this);
this.handleToggle = this.handleToggle.bind(this); this.handleToggle = this.handleToggle.bind(this);
this.handleWindowClick = this.handleWindowClick.bind(this); this.handleWindowClick = this.handleWindowClick.bind(this);
this.updateOrientation = this.updateOrientation.bind(this);
}
componentDidMount() {
window.addEventListener('resize', this.updateOrientation);
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@ -92,8 +106,6 @@ class Dropdown extends Component {
onHide, onHide,
keepOpen, keepOpen,
tethered, tethered,
sidebarContentPanel,
sidebarNavPanel
} = this.props; } = this.props;
const { isOpen } = this.state; const { isOpen } = this.state;
@ -113,16 +125,8 @@ class Dropdown extends Component {
if (prevProps.keepOpen && !keepOpen) onHide(); if (prevProps.keepOpen && !keepOpen) onHide();
} }
handleShow() { componentWillUnmount() {
Session.set('dropdownOpen', true); window.removeEventListener('resize', this.updateOrientation);
const {
onShow,
} = this.props;
this.setState({ isOpen: true }, () => {
const { addEventListener } = window;
onShow();
addEventListener('click', this.handleWindowClick, true);
});
} }
handleHide() { handleHide() {
@ -135,16 +139,17 @@ class Dropdown extends Component {
}); });
} }
componentDidMount() { handleShow() {
window.addEventListener('resize', this.updateOrientation); Session.set('dropdownOpen', true);
const {
onShow,
} = this.props;
this.setState({ isOpen: true }, () => {
const { addEventListener } = window;
onShow();
addEventListener('click', this.handleWindowClick, true);
});
} }
componentWillUnmount() {
window.removeEventListener('resize', this.updateOrientation);
}
updateOrientation = () => {
this.setState({ isPortrait:deviceInfo.isPortrait() });
};
handleWindowClick(event) { handleWindowClick(event) {
const { keepOpen, onHide } = this.props; const { keepOpen, onHide } = this.props;
@ -187,6 +192,10 @@ class Dropdown extends Component {
return isOpen ? this.handleHide() : this.handleShow(); return isOpen ? this.handleHide() : this.handleShow();
} }
updateOrientation() {
this.setState({ isPortrait: deviceInfo.isPortrait() });
}
render() { render() {
const { const {
children, children,
@ -198,7 +207,6 @@ class Dropdown extends Component {
getContent, getContent,
...otherProps ...otherProps
} = this.props; } = this.props;
const { isOpen, isPortrait } = this.state; const { isOpen, isPortrait } = this.state;
const { isPhone } = deviceInfo; const { isPhone } = deviceInfo;
const placements = placement && placement.replace(' ', '-'); const placements = placement && placement.replace(' ', '-');
@ -212,8 +220,8 @@ class Dropdown extends Component {
transform: '', transform: '',
}; };
let trigger = children.find(x => x.type === DropdownTrigger); let trigger = children.find((x) => x.type === DropdownTrigger);
let content = children.find(x => x.type === DropdownContent); let content = children.find((x) => x.type === DropdownContent);
trigger = React.cloneElement(trigger, { trigger = React.cloneElement(trigger, {
ref: (ref) => { this.trigger = ref; }, ref: (ref) => { this.trigger = ref; },
@ -238,7 +246,6 @@ class Dropdown extends Component {
}); });
const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null); const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null);
return ( return (
<div <div
className={cx(styles.dropdown, className)} className={cx(styles.dropdown, className)}
@ -270,11 +277,12 @@ class Dropdown extends Component {
to: 'scrollParent', to: 'scrollParent',
}, },
]} ]}
renderTarget={ref => ( renderTarget={(ref) => (
<span ref={ref}> <span ref={ref}>
{trigger} {trigger}
</span>)} </span>
renderElement={ref => ( )}
renderElement={(ref) => (
<div <div
ref={ref} ref={ref}
> >
@ -290,11 +298,12 @@ class Dropdown extends Component {
/> />
) : null} ) : null}
</div> </div>
) )}
} />
/>) )
: ( : (
<Fragment> // Fix eslint rule https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-fragments.md
<>
{trigger} {trigger}
{content} {content}
{showCloseBtn {showCloseBtn
@ -307,7 +316,7 @@ class Dropdown extends Component {
onClick={this.handleHide} onClick={this.handleHide}
/> />
) : null} ) : null}
</Fragment> </>
) )
} }
</div> </div>
@ -317,4 +326,11 @@ class Dropdown extends Component {
Dropdown.propTypes = propTypes; Dropdown.propTypes = propTypes;
Dropdown.defaultProps = defaultProps; Dropdown.defaultProps = defaultProps;
Dropdown.DropdownTrigger = DropdownTrigger;
Dropdown.DropdownContent = DropdownContent;
Dropdown.DropdownList = DropdownList;
Dropdown.DropdownListSeparator = DropdownListSeparator;
Dropdown.DropdownListItem = DropdownListItem;
Dropdown.DropdownListTitle = DropdownListTitle;
export default injectIntl(Dropdown, { forwardRef: true }); export default injectIntl(Dropdown, { forwardRef: true });

View File

@ -11,9 +11,9 @@ const propTypes = {
/* We should recheck this proptype, sometimes we need to create an container and send to /* We should recheck this proptype, sometimes we need to create an container and send to
dropdown, but with this proptype, is not possible. */ dropdown, but with this proptype, is not possible. */
children: PropTypes.arrayOf((propValue, key, componentName, propFullName) => { children: PropTypes.arrayOf((propValue, key, componentName, propFullName) => {
if (propValue[key].type !== ListItem && if (propValue[key].type !== ListItem
propValue[key].type !== ListSeparator && && propValue[key].type !== ListSeparator
propValue[key].type !== ListTitle) { && propValue[key].type !== ListTitle) {
return new Error(`Invalid prop \`${propFullName}\` supplied to` + return new Error(`Invalid prop \`${propFullName}\` supplied to` +
` \`${componentName}\`. Validation failed.`); ` \`${componentName}\`. Validation failed.`);
} }
@ -41,14 +41,13 @@ export default class DropdownList extends Component {
} }
componentDidMount() { componentDidMount() {
this._menu.addEventListener('keydown', event => this.handleItemKeyDown(event)); this._menu.addEventListener('keydown', (event) => this.handleItemKeyDown(event));
} }
componentDidUpdate() { componentDidUpdate() {
const { focusedIndex } = this.state; const { focusedIndex } = this.state;
const children = [].slice.call(this._menu.children); const children = [].slice.call(this._menu.children);
this.menuRefs = children.filter(child => child.getAttribute('role') === 'menuitem'); this.menuRefs = children.filter((child) => child.getAttribute('role') === 'menuitem');
const activeRef = this.menuRefs[focusedIndex]; const activeRef = this.menuRefs[focusedIndex];
@ -58,7 +57,10 @@ export default class DropdownList extends Component {
} }
handleItemKeyDown(event, callback) { handleItemKeyDown(event, callback) {
const { getDropdownMenuParent } = this.props; const {
getDropdownMenuParent,
horizontal,
} = this.props;
const { focusedIndex } = this.state; const { focusedIndex } = this.state;
let nextFocusedIndex = focusedIndex > 0 ? focusedIndex : 0; let nextFocusedIndex = focusedIndex > 0 ? focusedIndex : 0;
@ -67,7 +69,7 @@ export default class DropdownList extends Component {
nextFocusedIndex = this.menuRefs.indexOf(document.activeElement); nextFocusedIndex = this.menuRefs.indexOf(document.activeElement);
} }
const isHorizontal = this.props.horizontal; const isHorizontal = horizontal;
const navigationKeys = { const navigationKeys = {
previous: KEY_CODES[`ARROW_${isHorizontal ? 'LEFT' : 'UP'}`], previous: KEY_CODES[`ARROW_${isHorizontal ? 'LEFT' : 'UP'}`],
next: KEY_CODES[`ARROW_${isHorizontal ? 'RIGHT' : 'DOWN'}`], next: KEY_CODES[`ARROW_${isHorizontal ? 'RIGHT' : 'DOWN'}`],
@ -126,8 +128,14 @@ export default class DropdownList extends Component {
} }
handleItemClick(event, callback) { handleItemClick(event, callback) {
const { getDropdownMenuParent, onActionsHide, dropdownHide, keepOpen} = this.props; const {
if(!keepOpen) { getDropdownMenuParent,
onActionsHide,
dropdownHide,
keepOpen,
} = this.props;
if (!keepOpen) {
if (getDropdownMenuParent) { if (getDropdownMenuParent) {
onActionsHide(); onActionsHide();
} else { } else {
@ -142,7 +150,12 @@ export default class DropdownList extends Component {
} }
render() { render() {
const { children, style, className } = this.props; const {
children,
style,
className,
horizontal,
} = this.props;
const boundChildren = Children.map( const boundChildren = Children.map(
children, children,
@ -173,7 +186,7 @@ export default class DropdownList extends Component {
}, },
); );
const listDirection = this.props.horizontal ? styles.horizontalList : styles.verticalList; const listDirection = horizontal ? styles.horizontalList : styles.verticalList;
return ( return (
<ul <ul
style={style} style={style}

View File

@ -11,6 +11,7 @@ const propTypes = {
label: PropTypes.string, label: PropTypes.string,
description: PropTypes.string, description: PropTypes.string,
accessKey: PropTypes.string, accessKey: PropTypes.string,
tabIndex: PropTypes.number,
}; };
const defaultProps = { const defaultProps = {
@ -52,13 +53,21 @@ class DropdownListItem extends Component {
render() { render() {
const { const {
id, label, description, children, injectRef, tabIndex, onClick, onKeyDown, id,
className, style, intl, label,
description,
children,
injectRef,
tabIndex,
onClick,
onKeyDown,
className,
style,
intl,
} = this.props; } = this.props;
const isSelected = className && className.includes('emojiSelected'); const isSelected = className && className.includes('emojiSelected');
const _label = isSelected ? `${label} (${intl.formatMessage(messages.activeAriaLabel)})` : label; const _label = isSelected ? `${label} (${intl.formatMessage(messages.activeAriaLabel)})` : label;
return ( return (
<li <li
id={id} id={id}
@ -71,7 +80,6 @@ class DropdownListItem extends Component {
className={cx(styles.item, className)} className={cx(styles.item, className)}
style={style} style={style}
role="menuitem" role="menuitem"
data-test={this.props['data-test']}
> >
{ {
children || this.renderDefault() children || this.renderDefault()

View File

@ -3,10 +3,9 @@ import PropTypes from 'prop-types';
import cx from 'classnames'; import cx from 'classnames';
import { styles } from '../styles'; import { styles } from '../styles';
const DropdownListSeparator = ({ style, className }) => const DropdownListSeparator = ({ style, className }) => (
( <li style={style} className={cx(styles.separator, className)} />
<li style={style} className={cx(styles.separator, className)} /> );
);
DropdownListSeparator.propTypes = { DropdownListSeparator.propTypes = {
style: PropTypes.shape({}), style: PropTypes.shape({}),

View File

@ -1,13 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames'; import cx from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import { styles } from '../styles'; import { styles } from '../styles';
const propTypes = {
description: PropTypes.string,
};
export default class DropdownListTitle extends Component { export default class DropdownListTitle extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -15,14 +10,15 @@ export default class DropdownListTitle extends Component {
} }
render() { render() {
const { className } = this.props; const {
className,
children,
} = this.props;
return ( return (
<li className={cx(styles.title, className)} aria-hidden> <li className={cx(styles.title, className)} aria-hidden>
{this.props.children} {children}
</li> </li>
); );
} }
} }
DropdownListTitle.propTypes = propTypes;

View File

@ -14,11 +14,12 @@ export default class DropdownTrigger extends Component {
super(props); super(props);
this.handleClick = this.handleClick.bind(this); this.handleClick = this.handleClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
this.trigger = null;
} }
handleClick() { handleClick() {
const { dropdownToggle, onClick } = this.props; const { dropdownToggle, onClick } = this.props;
onClick && onClick(); if (onClick) onClick();
return dropdownToggle(); return dropdownToggle();
} }
@ -28,20 +29,15 @@ export default class DropdownTrigger extends Component {
if ([KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.which)) { if ([KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.which)) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} else if ([KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) {
return findDOMNode(this).click();
}
if ([KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) {
dropdownShow(); dropdownShow();
} } else if (KEY_CODES.ESCAPE === event.which) {
if (KEY_CODES.ESCAPE === event.which) {
dropdownHide(); dropdownHide();
} }
} }
render() { render() {
const { dropdownIsOpen } = this.props;
const remainingProps = { ...this.props }; const remainingProps = { ...this.props };
delete remainingProps.dropdownToggle; delete remainingProps.dropdownToggle;
delete remainingProps.dropdownShow; delete remainingProps.dropdownShow;
@ -58,10 +54,11 @@ export default class DropdownTrigger extends Component {
const TriggerComponentBounded = React.cloneElement(TriggerComponent, { const TriggerComponentBounded = React.cloneElement(TriggerComponent, {
...restProps, ...restProps,
ref: (ref) => { this.trigger = ref; },
onClick: this.handleClick, onClick: this.handleClick,
onKeyDown: this.handleKeyDown, onKeyDown: this.handleKeyDown,
className: cx(children.props.className, className), className: cx(children.props.className, className),
'aria-expanded': this.props.dropdownIsOpen, 'aria-expanded': dropdownIsOpen,
}); });
return TriggerComponentBounded; return TriggerComponentBounded;

View File

@ -9,11 +9,6 @@ import AboutContainer from '/imports/ui/components/about/container';
import SettingsMenuContainer from '/imports/ui/components/settings/container'; import SettingsMenuContainer from '/imports/ui/components/settings/container';
import Button from '/imports/ui/components/button/component'; import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import ShortcutHelpComponent from '/imports/ui/components/shortcut-help/component'; import ShortcutHelpComponent from '/imports/ui/components/shortcut-help/component';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import FullscreenService from '../../fullscreen-button/service'; import FullscreenService from '../../fullscreen-button/service';
@ -178,7 +173,7 @@ class SettingsDropdown extends PureComponent {
} }
return ( return (
<DropdownListItem <Dropdown.DropdownListItem
key="list-item-fullscreen" key="list-item-fullscreen"
icon={fullscreenIcon} icon={fullscreenIcon}
label={fullscreenLabel} label={fullscreenLabel}
@ -210,7 +205,7 @@ class SettingsDropdown extends PureComponent {
} = Meteor.settings.public.app; } = Meteor.settings.public.app;
const logoutOption = ( const logoutOption = (
<DropdownListItem <Dropdown.DropdownListItem
key="list-item-logout" key="list-item-logout"
data-test="logout" data-test="logout"
icon="logout" icon="logout"
@ -226,7 +221,7 @@ class SettingsDropdown extends PureComponent {
return _.compact([ return _.compact([
this.getFullscreenItem(), this.getFullscreenItem(),
(<DropdownListItem (<Dropdown.DropdownListItem
key="list-item-settings" key="list-item-settings"
icon="settings" icon="settings"
data-test="settings" data-test="settings"
@ -234,7 +229,7 @@ class SettingsDropdown extends PureComponent {
description={intl.formatMessage(intlMessages.settingsDesc)} description={intl.formatMessage(intlMessages.settingsDesc)}
onClick={() => mountModal(<SettingsMenuContainer />)} onClick={() => mountModal(<SettingsMenuContainer />)}
/>), />),
(<DropdownListItem (<Dropdown.DropdownListItem
key="list-item-about" key="list-item-about"
icon="about" icon="about"
label={intl.formatMessage(intlMessages.aboutLabel)} label={intl.formatMessage(intlMessages.aboutLabel)}
@ -243,7 +238,7 @@ class SettingsDropdown extends PureComponent {
/>), />),
!helpButton ? null !helpButton ? null
: ( : (
<DropdownListItem <Dropdown.DropdownListItem
key="list-item-help" key="list-item-help"
icon="help" icon="help"
iconRight="popout_window" iconRight="popout_window"
@ -252,16 +247,16 @@ class SettingsDropdown extends PureComponent {
onClick={() => window.open(`${helpLink}`)} onClick={() => window.open(`${helpLink}`)}
/> />
), ),
(<DropdownListItem (<Dropdown.DropdownListItem
key="list-item-shortcuts" key="list-item-shortcuts"
icon="shortcuts" icon="shortcuts"
label={intl.formatMessage(intlMessages.hotkeysLabel)} label={intl.formatMessage(intlMessages.hotkeysLabel)}
description={intl.formatMessage(intlMessages.hotkeysDesc)} description={intl.formatMessage(intlMessages.hotkeysDesc)}
onClick={() => mountModal(<ShortcutHelpComponent />)} onClick={() => mountModal(<ShortcutHelpComponent />)}
/>), />),
(isMeteorConnected ? <DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null), (isMeteorConnected ? <Dropdown.DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null),
allowedToEndMeeting && isMeteorConnected allowedToEndMeeting && isMeteorConnected
? (<DropdownListItem ? (<Dropdown.DropdownListItem
key="list-item-end-meeting" key="list-item-end-meeting"
icon="application" icon="application"
label={intl.formatMessage(intlMessages.endMeetingLabel)} label={intl.formatMessage(intlMessages.endMeetingLabel)}
@ -291,7 +286,7 @@ class SettingsDropdown extends PureComponent {
onShow={this.onActionsShow} onShow={this.onActionsShow}
onHide={this.onActionsHide} onHide={this.onActionsHide}
> >
<DropdownTrigger tabIndex={0} accessKey={OPEN_OPTIONS_AK}> <Dropdown.DropdownTrigger tabIndex={0} accessKey={OPEN_OPTIONS_AK}>
<Button <Button
label={intl.formatMessage(intlMessages.optionsLabel)} label={intl.formatMessage(intlMessages.optionsLabel)}
icon="more" icon="more"
@ -304,12 +299,12 @@ class SettingsDropdown extends PureComponent {
// even after the DropdownTrigger inject an onClick handler // even after the DropdownTrigger inject an onClick handler
onClick={() => null} onClick={() => null}
/> />
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent placement="bottom right"> <Dropdown.DropdownContent placement="bottom right">
<DropdownList> <Dropdown.DropdownList>
{this.renderMenuItems()} {this.renderMenuItems()}
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -4,11 +4,6 @@ import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom'; import { findDOMNode } from 'react-dom';
import UserAvatar from '/imports/ui/components/user-avatar/component'; import UserAvatar from '/imports/ui/components/user-avatar/component';
import Icon from '/imports/ui/components/icon/component'; import Icon from '/imports/ui/components/icon/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container'; import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
import { withModalMounter } from '/imports/ui/components/modal/service'; import { withModalMounter } from '/imports/ui/components/modal/service';
@ -293,7 +288,7 @@ class UserDropdown extends PureComponent {
)); ));
} }
actions.push(<DropdownListSeparator key={_.uniqueId('list-separator-')} />); actions.push(<Dropdown.DropdownListSeparator key={_.uniqueId('list-separator-')} />);
const statuses = Object.keys(getEmojiList); const statuses = Object.keys(getEmojiList);
statuses.map(status => actions.push(this.makeDropdownItem( statuses.map(status => actions.push(this.makeDropdownItem(
@ -465,7 +460,7 @@ class UserDropdown extends PureComponent {
makeDropdownItem(key, label, onClick, icon = null, iconRight = null) { makeDropdownItem(key, label, onClick, icon = null, iconRight = null) {
const { getEmoji } = this.props; const { getEmoji } = this.props;
return ( return (
<DropdownListItem <Dropdown.DropdownListItem
{...{ {...{
key, key,
label, label,
@ -672,24 +667,24 @@ class UserDropdown extends PureComponent {
getContent={dropdownContent => this.dropdownContent = dropdownContent} getContent={dropdownContent => this.dropdownContent = dropdownContent}
tethered tethered
> >
<DropdownTrigger> <Dropdown.DropdownTrigger>
{contents} {contents}
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent <Dropdown.DropdownContent
style={{ style={{
visibility: dropdownVisible ? 'visible' : 'hidden', visibility: dropdownVisible ? 'visible' : 'hidden',
}} }}
className={styles.dropdownContent} className={styles.dropdownContent}
placement={placement} placement={placement}
> >
<DropdownList <Dropdown.DropdownList
ref={(ref) => { this.list = ref; }} ref={(ref) => { this.list = ref; }}
getDropdownMenuParent={this.getDropdownMenuParent} getDropdownMenuParent={this.getDropdownMenuParent}
onActionsHide={this.onActionsHide} onActionsHide={this.onActionsHide}
> >
{actions} {actions}
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -1,281 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import cx from 'classnames';
import { defineMessages, injectIntl } from 'react-intl';
import deviceInfo from '/imports/utils/deviceInfo';
import Button from '/imports/ui/components/button/component';
import screenreaderTrap from 'makeup-screenreader-trap';
import TetherComponent from 'react-tether';
import { styles } from '/imports/ui/components/dropdown/styles';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
const intlMessages = defineMessages({
close: {
id: 'app.dropdown.close',
description: 'Close button label',
},
});
const noop = () => { };
const propTypes = {
/**
* The dropdown needs a trigger and a content component as children
*/
children: (props, propName, componentName) => {
const children = props[propName];
if (!children || children.length < 2) {
return new Error(`Invalid prop \`${propName}\` supplied to`
+ ` \`${componentName}\`. Validation failed.`);
}
const trigger = children.find(x => x.type === DropdownTrigger);
const content = children.find(x => x.type === DropdownContent);
if (!trigger) {
return new Error(`Invalid prop \`${propName}\` supplied to`
+ ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`);
}
if (!content) {
return new Error(`Invalid prop \`${propName}\` supplied to`
+ ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`);
}
return null;
},
isOpen: PropTypes.bool,
keepOpen: PropTypes.bool,
onHide: PropTypes.func,
onShow: PropTypes.func,
autoFocus: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
const defaultProps = {
children: null,
onShow: noop,
onHide: noop,
autoFocus: false,
isOpen: false,
keepOpen: null,
};
const attachments = {
'right-bottom': 'bottom left',
'right-top': 'bottom left',
};
const targetAttachments = {
'right-bottom': 'bottom right',
'right-top': 'top right',
};
class Dropdown extends Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.handleWindowClick = this.handleWindowClick.bind(this);
}
componentWillUpdate(nextProps, nextState) {
return nextState.isOpen ? screenreaderTrap.trap(this.dropdown) : screenreaderTrap.untrap();
}
componentDidUpdate(prevProps, prevState) {
const {
onShow,
onHide,
keepOpen,
} = this.props;
const { isOpen } = this.state;
if (isOpen && !prevState.isOpen) { onShow(); }
if (!isOpen && prevState.isOpen) { onHide(); }
if (prevProps.keepOpen && !keepOpen) { onHide(); }
}
handleShow() {
Session.set('dropdownOpen', true);
const {
onShow,
} = this.props;
this.setState({ isOpen: true }, () => {
const { addEventListener } = window;
onShow();
addEventListener('click', this.handleWindowClick, true);
});
}
handleHide() {
Session.set('dropdownOpen', false);
const { onHide } = this.props;
this.setState({ isOpen: false }, () => {
const { removeEventListener } = window;
onHide();
removeEventListener('click', this.handleWindowClick, true);
});
}
handleWindowClick(event) {
const { keepOpen, onHide } = this.props;
const { isOpen } = this.state;
const triggerElement = findDOMNode(this.trigger);
const contentElement = findDOMNode(this.content);
if (!(triggerElement && contentElement)) return;
if (triggerElement && triggerElement.contains(event.target)) {
if (keepOpen) {
onHide();
return;
}
if (isOpen) {
this.handleHide();
return;
}
}
if (keepOpen && isOpen && !contentElement.contains(event.target)) {
if (triggerElement) {
const { parentElement } = triggerElement;
if (parentElement) parentElement.focus();
}
onHide();
this.handleHide();
return;
}
if (keepOpen && triggerElement) {
const { parentElement } = triggerElement;
if (parentElement) parentElement.focus();
}
if (keepOpen !== null) return;
this.handleHide();
}
handleToggle() {
const { isOpen } = this.state;
return isOpen ? this.handleHide() : this.handleShow();
}
render() {
const {
children,
className,
intl,
keepOpen,
getContent,
placement,
...otherProps
} = this.props;
const { isOpen } = this.state;
const { isMobile } = deviceInfo;
let trigger = children.find(x => x.type === DropdownTrigger);
let content = children.find(x => x.type === DropdownContent);
trigger = React.cloneElement(trigger, {
ref: (ref) => { this.trigger = ref; },
dropdownIsOpen: isOpen,
dropdownToggle: this.handleToggle,
dropdownShow: this.handleShow,
dropdownHide: this.handleHide,
});
content = React.cloneElement(content, {
ref: (ref) => {
getContent(ref);
this.content = ref;
},
keepOpen,
'aria-expanded': isOpen,
dropdownIsOpen: isOpen,
dropdownToggle: this.handleToggle,
dropdownShow: this.handleShow,
dropdownHide: this.handleHide,
});
const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null);
const placements = placement.replace(' ', '-');
// workaround
const test = isMobile ? {
width: '100%',
height: '100%',
transform: 'translateY(0)',
} : {
width: '',
height: '',
transform: '',
};
return (
<div
className={cx(styles.dropdown, className)}
aria-live={otherProps['aria-live']}
aria-relevant={otherProps['aria-relevant']}
aria-haspopup={otherProps['aria-haspopup']}
aria-label={otherProps['aria-label']}
ref={(node) => { this.dropdown = node; }}
tabIndex={-1}
>
<TetherComponent
style={{
zIndex: isOpen ? 15 : '',
...test,
}}
attachment={
isMobile ? 'middle bottom'
: attachments[placements]
}
targetAttachment={
isMobile ? ''
: targetAttachments[placements]
}
constraints={[
{
to: 'scrollParent',
},
]}
renderTarget={ref => (
<span ref={ref}>
{trigger}
</span>)}
renderElement={ref => (
<div
ref={ref}
>
{content}
{showCloseBtn
? (
<Button
className={styles.close}
label={intl.formatMessage(intlMessages.close)}
size="lg"
color="default"
onClick={this.handleHide}
/>
) : null}
</div>
)
}
/>
</div>
);
}
}
Dropdown.propTypes = propTypes;
Dropdown.defaultProps = defaultProps;
export default injectIntl(Dropdown);

View File

@ -5,16 +5,11 @@ import _ from 'lodash';
import { withModalMounter } from '/imports/ui/components/modal/service'; import { withModalMounter } from '/imports/ui/components/modal/service';
import Button from '/imports/ui/components/button/component'; import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import LockViewersContainer from '/imports/ui/components/lock-viewers/container'; import LockViewersContainer from '/imports/ui/components/lock-viewers/container';
import GuestPolicyContainer from '/imports/ui/components/waiting-users/guest-policy/container'; import GuestPolicyContainer from '/imports/ui/components/waiting-users/guest-policy/container';
import BreakoutRoom from '/imports/ui/components/actions-bar/create-breakout-room/container'; import BreakoutRoom from '/imports/ui/components/actions-bar/create-breakout-room/container';
import CaptionsService from '/imports/ui/components/captions/service'; import CaptionsService from '/imports/ui/components/captions/service';
import CaptionsWriterMenu from '/imports/ui/components/captions/writer-menu/container'; import CaptionsWriterMenu from '/imports/ui/components/captions/writer-menu/container';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import { styles } from './styles'; import { styles } from './styles';
import { getUserNamesLink } from '/imports/ui/components/user-list/service'; import { getUserNamesLink } from '/imports/ui/components/user-list/service';
@ -244,7 +239,7 @@ class UserOptions extends PureComponent {
this.menuItems = _.compact([ this.menuItems = _.compact([
(isMeteorConnected ? ( (isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
key={this.clearStatusId} key={this.clearStatusId}
icon="clear_status" icon="clear_status"
label={intl.formatMessage(intlMessages.clearAllLabel)} label={intl.formatMessage(intlMessages.clearAllLabel)}
@ -254,7 +249,7 @@ class UserOptions extends PureComponent {
) : null ) : null
), ),
(!meetingIsBreakout && isMeteorConnected ? ( (!meetingIsBreakout && isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
key={this.muteAllId} key={this.muteAllId}
icon={isMeetingMuted ? 'unmute' : 'mute'} icon={isMeetingMuted ? 'unmute' : 'mute'}
label={intl.formatMessage(intlMessages[isMeetingMuted ? 'unmuteAllLabel' : 'muteAllLabel'])} label={intl.formatMessage(intlMessages[isMeetingMuted ? 'unmuteAllLabel' : 'muteAllLabel'])}
@ -264,7 +259,7 @@ class UserOptions extends PureComponent {
) : null ) : null
), ),
(!meetingIsBreakout && !isMeetingMuted && isMeteorConnected ? ( (!meetingIsBreakout && !isMeetingMuted && isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
key={this.muteId} key={this.muteId}
icon="mute" icon="mute"
label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)} label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)}
@ -275,7 +270,7 @@ class UserOptions extends PureComponent {
), ),
(amIModerator (amIModerator
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
icon="download" icon="download"
label={intl.formatMessage(intlMessages.saveUserNames)} label={intl.formatMessage(intlMessages.saveUserNames)}
key={this.saveUsersNameId} key={this.saveUsersNameId}
@ -285,7 +280,7 @@ class UserOptions extends PureComponent {
: null : null
), ),
(!meetingIsBreakout && isMeteorConnected ? ( (!meetingIsBreakout && isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
key={this.lockId} key={this.lockId}
icon="lock" icon="lock"
label={intl.formatMessage(intlMessages.lockViewersLabel)} label={intl.formatMessage(intlMessages.lockViewersLabel)}
@ -295,7 +290,7 @@ class UserOptions extends PureComponent {
) : null ) : null
), ),
(!meetingIsBreakout && isMeteorConnected && dynamicGuestPolicy ? ( (!meetingIsBreakout && isMeteorConnected && dynamicGuestPolicy ? (
<DropdownListItem <Dropdown.DropdownListItem
key={this.guestPolicyId} key={this.guestPolicyId}
icon="user" icon="user"
label={intl.formatMessage(intlMessages.guestPolicyLabel)} label={intl.formatMessage(intlMessages.guestPolicyLabel)}
@ -305,9 +300,9 @@ class UserOptions extends PureComponent {
/> />
) : null ) : null
), ),
(isMeteorConnected ? <DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null), (isMeteorConnected ? <Dropdown.DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null),
(canCreateBreakout && isMeteorConnected ? ( (canCreateBreakout && isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
data-test="createBreakoutRooms" data-test="createBreakoutRooms"
key={this.createBreakoutId} key={this.createBreakoutId}
icon="rooms" icon="rooms"
@ -318,7 +313,7 @@ class UserOptions extends PureComponent {
) : null ) : null
), ),
(canInviteUsers && isMeteorConnected ? ( (canInviteUsers && isMeteorConnected ? (
<DropdownListItem <Dropdown.DropdownListItem
data-test="inviteBreakoutRooms" data-test="inviteBreakoutRooms"
icon="rooms" icon="rooms"
label={intl.formatMessage(intlMessages.invitationItem)} label={intl.formatMessage(intlMessages.invitationItem)}
@ -329,7 +324,7 @@ class UserOptions extends PureComponent {
), ),
(amIModerator && CaptionsService.isCaptionsEnabled() && isMeteorConnected (amIModerator && CaptionsService.isCaptionsEnabled() && isMeteorConnected
? ( ? (
<DropdownListItem <Dropdown.DropdownListItem
icon="closed_caption" icon="closed_caption"
label={intl.formatMessage(intlMessages.captionsLabel)} label={intl.formatMessage(intlMessages.captionsLabel)}
description={intl.formatMessage(intlMessages.captionsDesc)} description={intl.formatMessage(intlMessages.captionsDesc)}
@ -356,7 +351,7 @@ class UserOptions extends PureComponent {
onHide={this.onActionsHide} onHide={this.onActionsHide}
className={styles.dropdown} className={styles.dropdown}
> >
<DropdownTrigger tabIndex={0}> <Dropdown.DropdownTrigger tabIndex={0}>
<Button <Button
label={intl.formatMessage(intlMessages.optionsLabel)} label={intl.formatMessage(intlMessages.optionsLabel)}
data-test="manageUsers" data-test="manageUsers"
@ -368,17 +363,17 @@ class UserOptions extends PureComponent {
size="sm" size="sm"
onClick={() => null} onClick={() => null}
/> />
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent <Dropdown.DropdownContent
className={styles.dropdownContent} className={styles.dropdownContent}
placement="right top" placement="right top"
> >
<DropdownList> <Dropdown.DropdownList>
{ {
this.renderMenuItems() this.renderMenuItems()
} }
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
); );
} }

View File

@ -5,12 +5,6 @@ import PropTypes from 'prop-types';
import _ from 'lodash'; import _ from 'lodash';
import cx from 'classnames'; import cx from 'classnames';
import Dropdown from '/imports/ui/components/dropdown/component'; import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import Icon from '/imports/ui/components/icon/component'; import Icon from '/imports/ui/components/icon/component';
import FullscreenService from '/imports/ui/components/fullscreen-button/service'; import FullscreenService from '/imports/ui/components/fullscreen-button/service';
import FullscreenButtonContainer from '/imports/ui/components/fullscreen-button/container'; import FullscreenButtonContainer from '/imports/ui/components/fullscreen-button/container';
@ -141,9 +135,9 @@ class VideoListItem extends Component {
} = this.props; } = this.props;
return _.compact([ return _.compact([
<DropdownListTitle className={styles.hiddenDesktop} key="name">{name}</DropdownListTitle>, <Dropdown.DropdownListTitle className={styles.hiddenDesktop} key="name">{name}</Dropdown.DropdownListTitle>,
<DropdownListSeparator className={styles.hiddenDesktop} key="sep" />, <Dropdown.DropdownListSeparator className={styles.hiddenDesktop} key="sep" />,
...actions.map(action => (<DropdownListItem key={`${cameraId}-${action.actionName}`} {...action} />)), ...actions.map(action => (<Dropdown.DropdownListItem key={`${cameraId}-${action.actionName}`} {...action} />)),
]); ]);
} }
@ -242,14 +236,14 @@ class VideoListItem extends Component {
{enableVideoMenu && availableActions.length >= 3 {enableVideoMenu && availableActions.length >= 3
? ( ? (
<Dropdown tethered={isTethered} placement="right bottom" className={isFirefox ? styles.dropdownFireFox : styles.dropdown}> <Dropdown tethered={isTethered} placement="right bottom" className={isFirefox ? styles.dropdownFireFox : styles.dropdown}>
<DropdownTrigger className={styles.dropdownTrigger}> <Dropdown.DropdownTrigger className={styles.dropdownTrigger}>
<span>{name}</span> <span>{name}</span>
</DropdownTrigger> </Dropdown.DropdownTrigger>
<DropdownContent placement="top left" className={styles.dropdownContent}> <Dropdown.DropdownContent placement="top left" className={styles.dropdownContent}>
<DropdownList className={styles.dropdownList}> <Dropdown.DropdownList className={styles.dropdownList}>
{availableActions} {availableActions}
</DropdownList> </Dropdown.DropdownList>
</DropdownContent> </Dropdown.DropdownContent>
</Dropdown> </Dropdown>
) )
: ( : (