Implements react tether in virtualizedlist
This commit is contained in:
parent
b451ce24f8
commit
d388fb070d
@ -1,6 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { findDOMNode } from 'react-dom';
|
import { findDOMNode } from 'react-dom';
|
||||||
|
import { isMobile } from 'react-device-detect';
|
||||||
|
import TetherComponent from 'react-tether';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { defineMessages, injectIntl, intlShape } from 'react-intl';
|
import { defineMessages, injectIntl, intlShape } from 'react-intl';
|
||||||
import Button from '/imports/ui/components/button/component';
|
import Button from '/imports/ui/components/button/component';
|
||||||
@ -16,7 +18,7 @@ const intlMessages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => { };
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
/**
|
/**
|
||||||
@ -51,6 +53,7 @@ const propTypes = {
|
|||||||
onShow: PropTypes.func,
|
onShow: PropTypes.func,
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
|
tethered: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@ -60,6 +63,16 @@ const defaultProps = {
|
|||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
keepOpen: null,
|
keepOpen: null,
|
||||||
|
getContent: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachments = {
|
||||||
|
'right-bottom': 'bottom left',
|
||||||
|
'right-top': 'bottom left',
|
||||||
|
};
|
||||||
|
const targetAttachments = {
|
||||||
|
'right-bottom': 'bottom right',
|
||||||
|
'right-top': 'top right',
|
||||||
};
|
};
|
||||||
|
|
||||||
class Dropdown extends Component {
|
class Dropdown extends Component {
|
||||||
@ -162,11 +175,25 @@ class Dropdown extends Component {
|
|||||||
className,
|
className,
|
||||||
intl,
|
intl,
|
||||||
keepOpen,
|
keepOpen,
|
||||||
|
tethered,
|
||||||
|
placement,
|
||||||
|
getContent,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { isOpen } = this.state;
|
const { isOpen } = this.state;
|
||||||
|
|
||||||
|
const placements = placement && placement.replace(' ', '-');
|
||||||
|
const test = isMobile ? {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
transform: 'translateY(0)',
|
||||||
|
} : {
|
||||||
|
width: '',
|
||||||
|
height: '',
|
||||||
|
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);
|
||||||
|
|
||||||
@ -176,15 +203,20 @@ class Dropdown extends Component {
|
|||||||
dropdownToggle: this.handleToggle,
|
dropdownToggle: this.handleToggle,
|
||||||
dropdownShow: this.handleShow,
|
dropdownShow: this.handleShow,
|
||||||
dropdownHide: this.handleHide,
|
dropdownHide: this.handleHide,
|
||||||
|
keepOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
content = React.cloneElement(content, {
|
content = React.cloneElement(content, {
|
||||||
ref: (ref) => { this.content = ref; },
|
ref: (ref) => {
|
||||||
|
getContent(ref);
|
||||||
|
this.content = ref;
|
||||||
|
},
|
||||||
'aria-expanded': isOpen,
|
'aria-expanded': isOpen,
|
||||||
dropdownIsOpen: isOpen,
|
dropdownIsOpen: isOpen,
|
||||||
dropdownToggle: this.handleToggle,
|
dropdownToggle: this.handleToggle,
|
||||||
dropdownShow: this.handleShow,
|
dropdownShow: this.handleShow,
|
||||||
dropdownHide: this.handleHide,
|
dropdownHide: this.handleHide,
|
||||||
|
keepOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null);
|
const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null);
|
||||||
@ -199,7 +231,35 @@ class Dropdown extends Component {
|
|||||||
ref={(node) => { this.dropdown = node; }}
|
ref={(node) => { this.dropdown = node; }}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
tethered ?
|
||||||
|
(
|
||||||
|
<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}
|
{trigger}
|
||||||
|
</span>)}
|
||||||
|
renderElement={ref => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
{showCloseBtn
|
{showCloseBtn
|
||||||
? (
|
? (
|
||||||
@ -212,6 +272,27 @@ class Dropdown extends Component {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>)
|
||||||
|
: (
|
||||||
|
<Fragment>
|
||||||
|
{trigger}
|
||||||
|
{content}
|
||||||
|
{showCloseBtn
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
className={styles.close}
|
||||||
|
label={intl.formatMessage(intlMessages.close)}
|
||||||
|
size="lg"
|
||||||
|
color="default"
|
||||||
|
onClick={this.handleHide}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,14 @@ const defaultProps = {
|
|||||||
export default class DropdownContent extends Component {
|
export default class DropdownContent extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
placement, children, className,
|
placement,
|
||||||
dropdownToggle, dropdownShow, dropdownHide, dropdownIsOpen,
|
children,
|
||||||
|
className,
|
||||||
|
dropdownToggle,
|
||||||
|
dropdownShow,
|
||||||
|
dropdownHide,
|
||||||
|
dropdownIsOpen,
|
||||||
|
keepOpen,
|
||||||
...restProps
|
...restProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -38,6 +44,7 @@ export default class DropdownContent extends Component {
|
|||||||
dropdownToggle,
|
dropdownToggle,
|
||||||
dropdownShow,
|
dropdownShow,
|
||||||
dropdownHide,
|
dropdownHide,
|
||||||
|
keepOpen,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -45,8 +45,8 @@ export default class DropdownList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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');
|
||||||
|
|
||||||
@ -126,14 +126,15 @@ export default class DropdownList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleItemClick(event, callback) {
|
handleItemClick(event, callback) {
|
||||||
const { getDropdownMenuParent, onActionsHide, dropdownHide } = this.props;
|
const { getDropdownMenuParent, onActionsHide, dropdownHide, keepOpen} = this.props;
|
||||||
|
if(!keepOpen) {
|
||||||
if (getDropdownMenuParent) {
|
if (getDropdownMenuParent) {
|
||||||
onActionsHide();
|
onActionsHide();
|
||||||
} else {
|
} else {
|
||||||
this.setState({ focusedIndex: null });
|
this.setState({ focusedIndex: null });
|
||||||
dropdownHide();
|
dropdownHide();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(event);
|
callback(event);
|
||||||
|
@ -417,14 +417,30 @@ const muteAllExceptPresenter = (userId) => { makeCall('muteAllExceptPresenter',
|
|||||||
|
|
||||||
const changeRole = (userId, role) => { makeCall('changeRole', userId, role); };
|
const changeRole = (userId, role) => { makeCall('changeRole', userId, role); };
|
||||||
|
|
||||||
const roving = (event, changeState, elementsList, element) => {
|
const focusFirstDropDownItem = () => {
|
||||||
|
const dropdownContent = document.querySelector('div[data-test="dropdownContent"][style="visibility: visible;"]');
|
||||||
|
if (!dropdownContent) return;
|
||||||
|
const list = dropdownContent.getElementsByTagName('li');
|
||||||
|
list[0].focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const roving = (...args) => {
|
||||||
|
const [
|
||||||
|
event,
|
||||||
|
changeState,
|
||||||
|
elementsList,
|
||||||
|
element,
|
||||||
|
] = args;
|
||||||
|
|
||||||
this.selectedElement = element;
|
this.selectedElement = element;
|
||||||
|
const numberOfChilds = elementsList.childElementCount;
|
||||||
const menuOpen = Session.get('dropdownOpen') || false;
|
const menuOpen = Session.get('dropdownOpen') || false;
|
||||||
|
|
||||||
if (menuOpen) {
|
if (menuOpen) {
|
||||||
const menuChildren = document.activeElement.getElementsByTagName('li');
|
const menuChildren = document.activeElement.getElementsByTagName('li');
|
||||||
|
|
||||||
if ([KEY_CODES.ESCAPE, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) {
|
if ([KEY_CODES.ESCAPE, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) {
|
||||||
|
Session.set('dropdownOpen', false);
|
||||||
document.activeElement.click();
|
document.activeElement.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,13 +461,15 @@ const roving = (event, changeState, elementsList, element) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) {
|
if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) {
|
||||||
|
Session.set('dropdownOpen', false);
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
changeState(null);
|
changeState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.keyCode === KEY_CODES.ARROW_DOWN) {
|
if (event.keyCode === KEY_CODES.ARROW_DOWN) {
|
||||||
const firstElement = elementsList.firstChild;
|
const firstElement = elementsList.firstChild;
|
||||||
let elRef = element ? element.nextSibling : firstElement;
|
let elRef = element && numberOfChilds > 1 ? element.nextSibling : firstElement;
|
||||||
|
|
||||||
elRef = elRef || firstElement;
|
elRef = elRef || firstElement;
|
||||||
changeState(elRef);
|
changeState(elRef);
|
||||||
}
|
}
|
||||||
@ -464,7 +482,10 @@ const roving = (event, changeState, elementsList, element) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.keyCode)) {
|
if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.keyCode)) {
|
||||||
document.activeElement.firstChild.click();
|
const tether = document.activeElement.firstChild;
|
||||||
|
const dropdownTrigger = tether.firstChild;
|
||||||
|
dropdownTrigger.click();
|
||||||
|
focusFirstDropDownItem();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -526,4 +547,5 @@ export default {
|
|||||||
hasPrivateChatBetweenUsers,
|
hasPrivateChatBetweenUsers,
|
||||||
toggleUserLock,
|
toggleUserLock,
|
||||||
requestUserInformation,
|
requestUserInformation,
|
||||||
|
focusFirstDropDownItem,
|
||||||
};
|
};
|
||||||
|
@ -70,16 +70,6 @@ class UserParticipants extends Component {
|
|||||||
this.rowRenderer = this.rowRenderer.bind(this);
|
this.rowRenderer = this.rowRenderer.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { compact } = this.props;
|
|
||||||
if (!compact) {
|
|
||||||
this.refScrollContainer.addEventListener(
|
|
||||||
'keydown',
|
|
||||||
this.rove,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const isPropsEqual = _.isEqual(this.props, nextProps);
|
const isPropsEqual = _.isEqual(this.props, nextProps);
|
||||||
const isStateEqual = _.isEqual(this.state, nextState);
|
const isStateEqual = _.isEqual(this.state, nextState);
|
||||||
@ -87,12 +77,20 @@ class UserParticipants extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { selectedUser } = this.state;
|
const { compact } = this.props;
|
||||||
if (selectedUser === prevState.selectedUser) return;
|
const { selectedUser, scrollArea } = this.state;
|
||||||
|
if (!compact && (!prevState.scrollArea && scrollArea)) {
|
||||||
|
scrollArea.addEventListener(
|
||||||
|
'keydown',
|
||||||
|
this.rove,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
const { firstChild } = selectedUser;
|
const { firstChild } = selectedUser;
|
||||||
if (firstChild) firstChild.focus();
|
if (!firstChild.isEqualNode(document.activeElement)) {
|
||||||
|
firstChild.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,8 +151,9 @@ class UserParticipants extends Component {
|
|||||||
|
|
||||||
rove(event) {
|
rove(event) {
|
||||||
const { roving } = this.props;
|
const { roving } = this.props;
|
||||||
const { selectedUser } = this.state;
|
const { selectedUser, scrollArea } = this.state;
|
||||||
const usersItemsRef = findDOMNode(this.refScrollItems);
|
const usersItemsRef = findDOMNode(scrollArea.firstChild);
|
||||||
|
|
||||||
roving(event, this.changeState, usersItemsRef, selectedUser);
|
roving(event, this.changeState, usersItemsRef, selectedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,8 +219,8 @@ class UserParticipants extends Component {
|
|||||||
this.listRef = ref;
|
this.listRef = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scrollArea) {
|
if (ref !== null && !scrollArea) {
|
||||||
this.setState({ scrollArea: findDOMNode(this.listRef) });
|
this.setState({ scrollArea: findDOMNode(ref) });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
rowHeight={this.cache.rowHeight}
|
rowHeight={this.cache.rowHeight}
|
||||||
|
@ -4,12 +4,12 @@ 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 Dropdown from './theteredDropdown/component';
|
|
||||||
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
||||||
import DropdownContent from '/imports/ui/components/dropdown/content/component';
|
import DropdownContent from '/imports/ui/components/dropdown/content/component';
|
||||||
import DropdownList from '/imports/ui/components/dropdown/list/component';
|
import DropdownList from '/imports/ui/components/dropdown/list/component';
|
||||||
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
||||||
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/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 lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@ -17,6 +17,7 @@ import { Session } from 'meteor/session';
|
|||||||
import { styles } from './styles';
|
import { styles } from './styles';
|
||||||
import UserName from '../user-name/component';
|
import UserName from '../user-name/component';
|
||||||
import UserIcons from '../user-icons/component';
|
import UserIcons from '../user-icons/component';
|
||||||
|
import Service from '../../../../service';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
presenter: {
|
presenter: {
|
||||||
@ -287,7 +288,10 @@ class UserDropdown extends PureComponent {
|
|||||||
{
|
{
|
||||||
showNestedOptions: true,
|
showNestedOptions: true,
|
||||||
isActionsOpen: true,
|
isActionsOpen: true,
|
||||||
}, Session.set('dropdownOpen', true),
|
}, () => {
|
||||||
|
Session.set('dropdownOpen', true);
|
||||||
|
Service.focusFirstDropDownItem();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
'user',
|
'user',
|
||||||
'right_arrow',
|
'right_arrow',
|
||||||
@ -601,7 +605,8 @@ class UserDropdown extends PureComponent {
|
|||||||
aria-live="assertive"
|
aria-live="assertive"
|
||||||
aria-relevant="additions"
|
aria-relevant="additions"
|
||||||
placement={placement}
|
placement={placement}
|
||||||
getContent={(dropdownContent) => this.dropdownContent = dropdownContent}
|
getContent={dropdownContent => this.dropdownContent = dropdownContent}
|
||||||
|
tethered
|
||||||
>
|
>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
{contents}
|
{contents}
|
||||||
|
@ -124,6 +124,12 @@
|
|||||||
@extend %text-elipsis;
|
@extend %text-elipsis;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
min-width: 10vw;
|
min-width: 10vw;
|
||||||
|
@include mq($medium-only) {
|
||||||
|
min-width: 13vw;
|
||||||
|
}
|
||||||
|
@include mq($large-up) {
|
||||||
|
min-width: 8vw;
|
||||||
|
}
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ class Dropdown extends Component {
|
|||||||
|
|
||||||
if (!isOpen && prevState.isOpen) { onHide(); }
|
if (!isOpen && prevState.isOpen) { onHide(); }
|
||||||
|
|
||||||
if (prevProps.keepOpen && !keepOpen) onHide();
|
if (prevProps.keepOpen && !keepOpen) { onHide(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShow() {
|
handleShow() {
|
||||||
@ -196,6 +196,7 @@ class Dropdown extends Component {
|
|||||||
getContent(ref);
|
getContent(ref);
|
||||||
this.content = ref;
|
this.content = ref;
|
||||||
},
|
},
|
||||||
|
keepOpen,
|
||||||
'aria-expanded': isOpen,
|
'aria-expanded': isOpen,
|
||||||
dropdownIsOpen: isOpen,
|
dropdownIsOpen: isOpen,
|
||||||
dropdownToggle: this.handleToggle,
|
dropdownToggle: this.handleToggle,
|
||||||
|
Loading…
Reference in New Issue
Block a user