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 Button from '/imports/ui/components/button/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 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 RandomUserSelectContainer from '/imports/ui/components/modal/random-user/container';
import cx from 'classnames';
@ -145,7 +140,7 @@ class ActionsDropdown extends PureComponent {
return _.compact([
(amIPresenter && isPollingEnabled
? (
<DropdownListItem
<Dropdown.DropdownListItem
icon="polling"
data-test="polling"
label={formatMessage(pollBtnLabel)}
@ -170,7 +165,7 @@ class ActionsDropdown extends PureComponent {
: null),
(!amIPresenter
? (
<DropdownListItem
<Dropdown.DropdownListItem
icon="presentation"
label={formatMessage(takePresenter)}
description={formatMessage(takePresenterDesc)}
@ -181,7 +176,7 @@ class ActionsDropdown extends PureComponent {
: null),
(amIPresenter
? (
<DropdownListItem
<Dropdown.DropdownListItem
data-test="uploadPresentation"
icon="presentation"
label={formatMessage(presentationLabel)}
@ -193,7 +188,7 @@ class ActionsDropdown extends PureComponent {
: null),
(amIPresenter && allowExternalVideo
? (
<DropdownListItem
<Dropdown.DropdownListItem
icon="video"
label={!isSharingVideo ? intl.formatMessage(intlMessages.startExternalVideoLabel)
: intl.formatMessage(intlMessages.stopExternalVideoLabel)}
@ -205,7 +200,7 @@ class ActionsDropdown extends PureComponent {
: null),
(amIPresenter && isSelectRandomUserEnabled
? (
<DropdownListItem
<Dropdown.DropdownListItem
icon="user"
label={intl.formatMessage(intlMessages.selectRandUserLabel)}
description={intl.formatMessage(intlMessages.selectRandUserDesc)}
@ -238,7 +233,7 @@ class ActionsDropdown extends PureComponent {
itemStyles[styles.isCurrent] = p.current;
return (
<DropdownListItem
<Dropdown.DropdownListItem
className={cx(itemStyles)}
icon="file"
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;
}
@ -288,7 +283,7 @@ class ActionsDropdown extends PureComponent {
className={styles.dropdown}
ref={(ref) => { this._dropdown = ref; }}
>
<DropdownTrigger tabIndex={0} accessKey={OPEN_ACTIONS_AK}>
<Dropdown.DropdownTrigger tabIndex={0} accessKey={OPEN_ACTIONS_AK}>
<Button
className={isDropdownOpen ? styles.hideDropdownButton : ''}
hideLabel
@ -300,12 +295,12 @@ class ActionsDropdown extends PureComponent {
circle
onClick={() => null}
/>
</DropdownTrigger>
<DropdownContent placement="top left">
<DropdownList className={styles.scrollableList}>
</Dropdown.DropdownTrigger>
<Dropdown.DropdownContent placement="top left">
<Dropdown.DropdownList className={styles.scrollableList}>
{children}
</DropdownList>
</DropdownContent>
</Dropdown.DropdownList>
</Dropdown.DropdownContent>
</Dropdown>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,11 +14,12 @@ export default class DropdownTrigger extends Component {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.trigger = null;
}
handleClick() {
const { dropdownToggle, onClick } = this.props;
onClick && onClick();
if (onClick) onClick();
return dropdownToggle();
}
@ -28,20 +29,15 @@ export default class DropdownTrigger extends Component {
if ([KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.which)) {
event.preventDefault();
event.stopPropagation();
return findDOMNode(this).click();
}
if ([KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) {
} else if ([KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) {
dropdownShow();
}
if (KEY_CODES.ESCAPE === event.which) {
} else if (KEY_CODES.ESCAPE === event.which) {
dropdownHide();
}
}
render() {
const { dropdownIsOpen } = this.props;
const remainingProps = { ...this.props };
delete remainingProps.dropdownToggle;
delete remainingProps.dropdownShow;
@ -58,10 +54,11 @@ export default class DropdownTrigger extends Component {
const TriggerComponentBounded = React.cloneElement(TriggerComponent, {
...restProps,
ref: (ref) => { this.trigger = ref; },
onClick: this.handleClick,
onKeyDown: this.handleKeyDown,
className: cx(children.props.className, className),
'aria-expanded': this.props.dropdownIsOpen,
'aria-expanded': dropdownIsOpen,
});
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 Button from '/imports/ui/components/button/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 withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import FullscreenService from '../../fullscreen-button/service';
@ -178,7 +173,7 @@ class SettingsDropdown extends PureComponent {
}
return (
<DropdownListItem
<Dropdown.DropdownListItem
key="list-item-fullscreen"
icon={fullscreenIcon}
label={fullscreenLabel}
@ -210,7 +205,7 @@ class SettingsDropdown extends PureComponent {
} = Meteor.settings.public.app;
const logoutOption = (
<DropdownListItem
<Dropdown.DropdownListItem
key="list-item-logout"
data-test="logout"
icon="logout"
@ -226,7 +221,7 @@ class SettingsDropdown extends PureComponent {
return _.compact([
this.getFullscreenItem(),
(<DropdownListItem
(<Dropdown.DropdownListItem
key="list-item-settings"
icon="settings"
data-test="settings"
@ -234,7 +229,7 @@ class SettingsDropdown extends PureComponent {
description={intl.formatMessage(intlMessages.settingsDesc)}
onClick={() => mountModal(<SettingsMenuContainer />)}
/>),
(<DropdownListItem
(<Dropdown.DropdownListItem
key="list-item-about"
icon="about"
label={intl.formatMessage(intlMessages.aboutLabel)}
@ -243,7 +238,7 @@ class SettingsDropdown extends PureComponent {
/>),
!helpButton ? null
: (
<DropdownListItem
<Dropdown.DropdownListItem
key="list-item-help"
icon="help"
iconRight="popout_window"
@ -252,16 +247,16 @@ class SettingsDropdown extends PureComponent {
onClick={() => window.open(`${helpLink}`)}
/>
),
(<DropdownListItem
(<Dropdown.DropdownListItem
key="list-item-shortcuts"
icon="shortcuts"
label={intl.formatMessage(intlMessages.hotkeysLabel)}
description={intl.formatMessage(intlMessages.hotkeysDesc)}
onClick={() => mountModal(<ShortcutHelpComponent />)}
/>),
(isMeteorConnected ? <DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null),
(isMeteorConnected ? <Dropdown.DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null),
allowedToEndMeeting && isMeteorConnected
? (<DropdownListItem
? (<Dropdown.DropdownListItem
key="list-item-end-meeting"
icon="application"
label={intl.formatMessage(intlMessages.endMeetingLabel)}
@ -291,7 +286,7 @@ class SettingsDropdown extends PureComponent {
onShow={this.onActionsShow}
onHide={this.onActionsHide}
>
<DropdownTrigger tabIndex={0} accessKey={OPEN_OPTIONS_AK}>
<Dropdown.DropdownTrigger tabIndex={0} accessKey={OPEN_OPTIONS_AK}>
<Button
label={intl.formatMessage(intlMessages.optionsLabel)}
icon="more"
@ -304,12 +299,12 @@ class SettingsDropdown extends PureComponent {
// even after the DropdownTrigger inject an onClick handler
onClick={() => null}
/>
</DropdownTrigger>
<DropdownContent placement="bottom right">
<DropdownList>
</Dropdown.DropdownTrigger>
<Dropdown.DropdownContent placement="bottom right">
<Dropdown.DropdownList>
{this.renderMenuItems()}
</DropdownList>
</DropdownContent>
</Dropdown.DropdownList>
</Dropdown.DropdownContent>
</Dropdown>
);
}

View File

@ -4,11 +4,6 @@ import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import UserAvatar from '/imports/ui/components/user-avatar/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 lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
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);
statuses.map(status => actions.push(this.makeDropdownItem(
@ -465,7 +460,7 @@ class UserDropdown extends PureComponent {
makeDropdownItem(key, label, onClick, icon = null, iconRight = null) {
const { getEmoji } = this.props;
return (
<DropdownListItem
<Dropdown.DropdownListItem
{...{
key,
label,
@ -672,24 +667,24 @@ class UserDropdown extends PureComponent {
getContent={dropdownContent => this.dropdownContent = dropdownContent}
tethered
>
<DropdownTrigger>
<Dropdown.DropdownTrigger>
{contents}
</DropdownTrigger>
<DropdownContent
</Dropdown.DropdownTrigger>
<Dropdown.DropdownContent
style={{
visibility: dropdownVisible ? 'visible' : 'hidden',
}}
className={styles.dropdownContent}
placement={placement}
>
<DropdownList
<Dropdown.DropdownList
ref={(ref) => { this.list = ref; }}
getDropdownMenuParent={this.getDropdownMenuParent}
onActionsHide={this.onActionsHide}
>
{actions}
</DropdownList>
</DropdownContent>
</Dropdown.DropdownList>
</Dropdown.DropdownContent>
</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 Button from '/imports/ui/components/button/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 GuestPolicyContainer from '/imports/ui/components/waiting-users/guest-policy/container';
import BreakoutRoom from '/imports/ui/components/actions-bar/create-breakout-room/container';
import CaptionsService from '/imports/ui/components/captions/service';
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 { getUserNamesLink } from '/imports/ui/components/user-list/service';
@ -244,7 +239,7 @@ class UserOptions extends PureComponent {
this.menuItems = _.compact([
(isMeteorConnected ? (
<DropdownListItem
<Dropdown.DropdownListItem
key={this.clearStatusId}
icon="clear_status"
label={intl.formatMessage(intlMessages.clearAllLabel)}
@ -254,7 +249,7 @@ class UserOptions extends PureComponent {
) : null
),
(!meetingIsBreakout && isMeteorConnected ? (
<DropdownListItem
<Dropdown.DropdownListItem
key={this.muteAllId}
icon={isMeetingMuted ? 'unmute' : 'mute'}
label={intl.formatMessage(intlMessages[isMeetingMuted ? 'unmuteAllLabel' : 'muteAllLabel'])}
@ -264,7 +259,7 @@ class UserOptions extends PureComponent {
) : null
),
(!meetingIsBreakout && !isMeetingMuted && isMeteorConnected ? (
<DropdownListItem
<Dropdown.DropdownListItem
key={this.muteId}
icon="mute"
label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)}
@ -275,7 +270,7 @@ class UserOptions extends PureComponent {
),
(amIModerator
? (
<DropdownListItem
<Dropdown.DropdownListItem
icon="download"
label={intl.formatMessage(intlMessages.saveUserNames)}
key={this.saveUsersNameId}
@ -285,7 +280,7 @@ class UserOptions extends PureComponent {
: null
),
(!meetingIsBreakout && isMeteorConnected ? (
<DropdownListItem
<Dropdown.DropdownListItem
key={this.lockId}
icon="lock"
label={intl.formatMessage(intlMessages.lockViewersLabel)}
@ -295,7 +290,7 @@ class UserOptions extends PureComponent {
) : null
),
(!meetingIsBreakout && isMeteorConnected && dynamicGuestPolicy ? (
<DropdownListItem
<Dropdown.DropdownListItem
key={this.guestPolicyId}
icon="user"
label={intl.formatMessage(intlMessages.guestPolicyLabel)}
@ -305,9 +300,9 @@ class UserOptions extends PureComponent {
/>
) : null
),
(isMeteorConnected ? <DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null),
(isMeteorConnected ? <Dropdown.DropdownListSeparator key={_.uniqueId('list-separator-')} /> : null),
(canCreateBreakout && isMeteorConnected ? (
<DropdownListItem
<Dropdown.DropdownListItem
data-test="createBreakoutRooms"
key={this.createBreakoutId}
icon="rooms"
@ -318,7 +313,7 @@ class UserOptions extends PureComponent {
) : null
),
(canInviteUsers && isMeteorConnected ? (
<DropdownListItem
<Dropdown.DropdownListItem
data-test="inviteBreakoutRooms"
icon="rooms"
label={intl.formatMessage(intlMessages.invitationItem)}
@ -329,7 +324,7 @@ class UserOptions extends PureComponent {
),
(amIModerator && CaptionsService.isCaptionsEnabled() && isMeteorConnected
? (
<DropdownListItem
<Dropdown.DropdownListItem
icon="closed_caption"
label={intl.formatMessage(intlMessages.captionsLabel)}
description={intl.formatMessage(intlMessages.captionsDesc)}
@ -356,7 +351,7 @@ class UserOptions extends PureComponent {
onHide={this.onActionsHide}
className={styles.dropdown}
>
<DropdownTrigger tabIndex={0}>
<Dropdown.DropdownTrigger tabIndex={0}>
<Button
label={intl.formatMessage(intlMessages.optionsLabel)}
data-test="manageUsers"
@ -368,17 +363,17 @@ class UserOptions extends PureComponent {
size="sm"
onClick={() => null}
/>
</DropdownTrigger>
<DropdownContent
</Dropdown.DropdownTrigger>
<Dropdown.DropdownContent
className={styles.dropdownContent}
placement="right top"
>
<DropdownList>
<Dropdown.DropdownList>
{
this.renderMenuItems()
}
</DropdownList>
</DropdownContent>
</Dropdown.DropdownList>
</Dropdown.DropdownContent>
</Dropdown>
);
}

View File

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