Add proptypes and a few more refactors

This commit is contained in:
Klaus 2017-08-17 11:18:02 -03:00
parent c87f3054b9
commit 4fa10ec656
5 changed files with 111 additions and 65 deletions

View File

@ -1,8 +1,9 @@
import React, { Component, Children, cloneElement } from 'react'; import React, { Component, Children, cloneElement } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styles from './styles';
import cx from 'classnames'; import cx from 'classnames';
import UserAction from '/imports/ui/components/user-list/user-list-item/user-action/component';
import KEY_CODES from '/imports/utils/keyCodes'; import KEY_CODES from '/imports/utils/keyCodes';
import styles from './styles';
import ListItem from './item/component'; import ListItem from './item/component';
import ListSeparator from './separator/component'; import ListSeparator from './separator/component';
import ListTitle from './title/component'; import ListTitle from './title/component';
@ -10,14 +11,16 @@ import ListTitle from './title/component';
const propTypes = { const propTypes = {
children: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => { children: PropTypes.arrayOf((propValue, key, componentName, location, 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 &&
propValue[key].type !== UserAction) {
return new Error( return new Error(
`Invalid prop \`${propFullName}\` supplied to` + `Invalid prop \`${propFullName}\` supplied to` +
` \`${componentName}\`. Validation failed.`, ` \`${componentName}\`. Validation failed.`,
); );
} }
}), return true;
}).isRequired,
}; };
export default class DropdownList extends Component { export default class DropdownList extends Component {
@ -29,36 +32,31 @@ export default class DropdownList extends Component {
this.handleItemClick = this.handleItemClick.bind(this); this.handleItemClick = this.handleItemClick.bind(this);
} }
componentDidMount() {
this._menu.addEventListener('keydown', event=>this.handleItemKeyDown(event));
}
componentWillMount() { componentWillMount() {
this.setState({ this.setState({
focusedIndex: 0, focusedIndex: 0,
}); });
} }
componentDidUpdate(prevProps, prevState) {
let { focusedIndex } = this.state;
this.menuRefs = []; componentDidMount() {
this._menu.addEventListener('keydown', event => this.handleItemKeyDown(event));
}
for (let i = 0; i < (this._menu.children.length); i++) { componentDidUpdate() {
if (this._menu.children[i].getAttribute("role") === 'menuitem') { const { focusedIndex } = this.state;
this.menuRefs.push(this._menu.children[i]);
} const childrens = [].slice.call(this._menu.children);
} this.menuRefs = childrens.filter(child => child.getAttribute('role') === 'menuitem');
const activeRef = this.menuRefs[focusedIndex]; const activeRef = this.menuRefs[focusedIndex];
if (activeRef) { if (activeRef) {
activeRef.focus(); activeRef.focus();
} }
} }
handleItemKeyDown(event, callback) { handleItemKeyDown(event, callback) {
const { onActionsHide, getDropdownMenuParent, } = this.props; const { getDropdownMenuParent } = this.props;
let nextFocusedIndex = this.state.focusedIndex; let nextFocusedIndex = this.state.focusedIndex;
if (KEY_CODES.ARROW_UP === event.which) { if (KEY_CODES.ARROW_UP === event.which) {
@ -68,7 +66,7 @@ export default class DropdownList extends Component {
if (nextFocusedIndex < 0) { if (nextFocusedIndex < 0) {
nextFocusedIndex = this.menuRefs.length - 1; nextFocusedIndex = this.menuRefs.length - 1;
}else if (nextFocusedIndex > this.menuRefs.length - 1) { } else if (nextFocusedIndex > this.menuRefs.length - 1) {
nextFocusedIndex = 0; nextFocusedIndex = 0;
} }
} }
@ -90,30 +88,29 @@ export default class DropdownList extends Component {
if ([KEY_CODES.ESCAPE, KEY_CODES.TAB, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) { if ([KEY_CODES.ESCAPE, KEY_CODES.TAB, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) {
const { dropdownHide } = this.props; const { dropdownHide } = this.props;
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
dropdownHide(); dropdownHide();
if (getDropdownMenuParent) { if (getDropdownMenuParent) {
getDropdownMenuParent().focus(); getDropdownMenuParent().focus();
} }
} }
this.setState({focusedIndex: nextFocusedIndex}); this.setState({ focusedIndex: nextFocusedIndex });
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(event); callback(event);
} }
} }
handleItemClick(event, callback) { handleItemClick(event, callback) {
const { getDropdownMenuParent, onActionsHide} = this.props; const { getDropdownMenuParent, onActionsHide, dropdownHide } = this.props;
const { dropdownHide } = this.props;
if ( getDropdownMenuParent ) { if (getDropdownMenuParent) {
onActionsHide(); onActionsHide();
}else{ } else {
this.setState({ focusedIndex: null }); this.setState({ focusedIndex: null });
dropdownHide(); dropdownHide();
} }
@ -127,7 +124,7 @@ export default class DropdownList extends Component {
const { children, style, className } = this.props; const { children, style, className } = this.props;
const boundChildren = Children.map(children, const boundChildren = Children.map(children,
(item, i) => { (item) => {
if (item.type === ListSeparator) { if (item.type === ListSeparator) {
return item; return item;
} }
@ -140,13 +137,13 @@ export default class DropdownList extends Component {
onClick: (event) => { onClick: (event) => {
let { onClick } = item.props; let { onClick } = item.props;
onClick = onClick ? onClick.bind(item) : null; onClick = onClick ? () => onClick.call(item) : null;
this.handleItemClick(event, onClick); this.handleItemClick(event, onClick);
}, },
onKeyDown: (event) => { onKeyDown: (event) => {
let { onKeyDown } = item.props; let { onKeyDown } = item.props;
onKeyDown = onKeyDown ? onKeyDown.bind(item) : null; onKeyDown = onKeyDown ? () => onKeyDown.call(item) : null;
this.handleItemKeyDown(event, onKeyDown); this.handleItemKeyDown(event, onKeyDown);
}, },
@ -154,11 +151,16 @@ export default class DropdownList extends Component {
}); });
return ( return (
<ul <ul
style={style} style={style}
className={cx(styles.list, className)} className={cx(styles.list, className)}
role="menu" ref={(r) => this._menu = r}> role="menu"
{boundChildren} ref={(r) => {
this._menu = r;
return r;
}}
>
{boundChildren}
</ul> </ul>
); );
} }

View File

@ -15,6 +15,7 @@ const defaultProps = {
icon: '', icon: '',
label: '', label: '',
description: '', description: '',
tabIndex: 0,
}; };
export default class DropdownListItem extends Component { export default class DropdownListItem extends Component {

View File

@ -2,9 +2,9 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import _ from 'lodash'; import _ from 'lodash';
import UserListContent from './user-list-content/component'; import UserListContent from './user-list-content/component';
import UserAction from './user-action/component';
const normalizeEmojiName = (emoji) => { const normalizeEmojiName = (emoji) => {
const emojisNormalized = { const emojisNormalized = {
@ -32,14 +32,33 @@ const propTypes = {
currentUser: PropTypes.shape({ currentUser: PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
}).isRequired, }).isRequired,
compact: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
userActions: PropTypes.object.isRequired,
router: PropTypes.object.isRequired,
isBreakoutRoom: PropTypes.bool.isRequired,
getAvailableActions: PropTypes.func.isRequired,
}; };
const defaultProps = { const defaultProps = {
shouldShowActions: false, shouldShowActions: false,
isBreakoutRoom: false,
}; };
class UserListItem extends Component { class UserListItem extends Component {
static createAction(action, ...options) {
return (
<UserAction
key={_.uniqueId('action-item-')}
icon={action.icon}
label={action.label}
handler={action.handler}
options={[...options]}
/>
);
}
getUsersActions() { getUsersActions() {
const { const {
currentUser, currentUser,
@ -69,32 +88,16 @@ class UserListItem extends Component {
allowedToKick, allowedToKick,
allowedToSetPresenter } = actions; allowedToSetPresenter } = actions;
return _.compact([ return _.compact([
(allowedToChatPrivately ? this.renderUserAction(openChat, router, user) : null), (allowedToChatPrivately ? UserListItem.createAction(openChat, router, user) : null),
(allowedToMuteAudio ? this.renderUserAction(unmute, user) : null), (allowedToMuteAudio ? UserListItem.createAction(unmute, user) : null),
(allowedToUnmuteAudio ? this.renderUserAction(mute, user) : null), (allowedToUnmuteAudio ? UserListItem.createAction(mute, user) : null),
(allowedToResetStatus ? this.renderUserAction(clearStatus, user) : null), (allowedToResetStatus ? UserListItem.createAction(clearStatus, user) : null),
(allowedToSetPresenter ? this.renderUserAction(setPresenter, user) : null), (allowedToSetPresenter ? UserListItem.createAction(setPresenter, user) : null),
(allowedToKick ? this.renderUserAction(kick, user) : null), (allowedToKick ? UserListItem.createAction(kick, user) : null),
]); ]);
} }
renderUserAction(action, ...parameters) {
const userAction = (
<DropdownListItem
key={_.uniqueId('action-item-')}
icon={action.icon}
label={action.label}
defaultMessage={action.label}
onClick={action.handler.bind(this, ...parameters)}
/>
);
return userAction;
}
render() { render() {
const { const {
compact, compact,

View File

@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
const propTypes = {
icon: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
handler: PropTypes.func.isRequired,
options: PropTypes.array.isRequired,
};
const UserActions = (props) => {
const { key, icon, label, handler, options } = props;
const userAction = (
<DropdownListItem
key={key}
icon={icon}
label={label}
defaultMessage={label}
onClick={() => handler.call(this, ...options)}
/>
);
return userAction;
};
UserActions.propTypes = propTypes;
export default UserActions;

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import cx from 'classnames'; import cx from 'classnames';
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';
@ -41,6 +42,15 @@ const messages = defineMessages({
}, },
}); });
const propTypes = {
compact: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired,
intl: PropTypes.object.isRequired,
normalizeEmojiName: PropTypes.func.isRequired,
actions: PropTypes.array.isRequired,
};
class UserListContent extends Component { class UserListContent extends Component {
/** /**
@ -108,7 +118,6 @@ class UserListContent extends Component {
}); });
} }
/** /**
* Check if the dropdown is visible, if so, check if should be draw on top or bottom direction. * Check if the dropdown is visible, if so, check if should be draw on top or bottom direction.
*/ */
@ -126,7 +135,7 @@ class UserListContent extends Component {
const isDropdownVisible = const isDropdownVisible =
UserListContent.checkIfDropdownIsVisible(dropdownContent.offsetTop, UserListContent.checkIfDropdownIsVisible(dropdownContent.offsetTop,
dropdownContent.offsetHeight); dropdownContent.offsetHeight);
if (!isDropdownVisible) { if (!isDropdownVisible) {
const offsetPageTop = const offsetPageTop =
@ -159,19 +168,20 @@ class UserListContent extends Component {
render() { render() {
const { const {
compact, compact,
user, user,
intl, intl,
normalizeEmojiName, normalizeEmojiName,
actions, actions,
} = this.props; } = this.props;
const { const {
isActionsOpen, isActionsOpen,
dropdownVisible, dropdownVisible,
dropdownDirection, dropdownDirection,
dropdownOffset, dropdownOffset,
} = this.state; } = this.state;
const userItemContentsStyle = {}; const userItemContentsStyle = {};
userItemContentsStyle[styles.userItemContentsCompact] = compact; userItemContentsStyle[styles.userItemContentsCompact] = compact;
@ -276,4 +286,5 @@ class UserListContent extends Component {
} }
} }
UserListContent.propTypes = propTypes;
export default UserListContent; export default UserListContent;