mirror of https://github.com/mastodon/mastodon
fix(dropdown_menu): Open as modal on mobile (#4295)
* fix(dropdown_menu): Open as modal on mobile * fix(dropdown_menu): Open modal on touch * fix(dropdown_menu): Show status * fix(dropdown_menu): Max dimensions and reduce padding * chore(dropdown_menu): Test new functionality * refactor: Use DropdownMenuContainer instead of DropdownMenu * feat(privacy_dropdown): Open as modal on touch devices * feat(modal_root): Do not load actions-modal async
This commit is contained in:
parent
aa803153e2
commit
50d38d7605
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -9,16 +10,23 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
};
|
||||
|
||||
static propTypes = {
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
icon: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
direction: PropTypes.string,
|
||||
status: ImmutablePropTypes.map,
|
||||
ariaLabel: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
ariaLabel: 'Menu',
|
||||
isModalOpen: false,
|
||||
isUserTouching: () => false,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -34,6 +42,10 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const { action, to } = this.props.items[i];
|
||||
|
||||
if (this.props.isModalOpen) {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
// Don't call e.preventDefault() when the item uses 'href' property.
|
||||
// ex. "Edit profile" on the account action bar
|
||||
|
||||
|
@ -48,7 +60,17 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
handleShow = () => this.setState({ expanded: true })
|
||||
handleShow = () => {
|
||||
if (this.props.isUserTouching()) {
|
||||
this.props.onModalOpen({
|
||||
status: this.props.status,
|
||||
actions: this.props.items,
|
||||
onClick: this.handleClick,
|
||||
});
|
||||
} else {
|
||||
this.setState({ expanded: true });
|
||||
}
|
||||
}
|
||||
|
||||
handleHide = () => this.setState({ expanded: false })
|
||||
|
||||
|
@ -71,6 +93,7 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
render () {
|
||||
const { icon, items, size, direction, ariaLabel, disabled } = this.props;
|
||||
const { expanded } = this.state;
|
||||
const isUserTouching = this.props.isUserTouching();
|
||||
const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right';
|
||||
const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` };
|
||||
const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`;
|
||||
|
@ -89,15 +112,21 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
</ul>
|
||||
);
|
||||
|
||||
// No need to render the actual dropdown if we use the modal. If we
|
||||
// don't render anything <Dropdow /> breaks, so we just put an empty div.
|
||||
const dropdownContent = !isUserTouching ? (
|
||||
<DropdownContent className={directionClass}>
|
||||
{dropdownItems}
|
||||
</DropdownContent>
|
||||
) : <div />;
|
||||
|
||||
return (
|
||||
<Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}>
|
||||
<Dropdown ref={this.setRef} active={isUserTouching ? false : undefined} onShow={this.handleShow} onHide={this.handleHide}>
|
||||
<DropdownTrigger className='icon-button' style={iconStyle} aria-label={ariaLabel}>
|
||||
<i className={iconClassname} aria-hidden />
|
||||
</DropdownTrigger>
|
||||
|
||||
<DropdownContent className={directionClass}>
|
||||
{dropdownItems}
|
||||
</DropdownContent>
|
||||
{dropdownContent}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import IconButton from './icon_button';
|
||||
import DropdownMenu from './dropdown_menu';
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
|
@ -156,7 +156,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||
{shareButton}
|
||||
|
||||
<div className='status__action-bar-dropdown'>
|
||||
<DropdownMenu disabled={anonymousAccess} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
|
||||
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { openModal, closeModal } from '../actions/modal';
|
||||
import { connect } from 'react-redux';
|
||||
import DropdownMenu from '../components/dropdown_menu';
|
||||
import { isUserTouching } from '../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
isUserTouching,
|
||||
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
||||
onModalClose: () => dispatch(closeModal()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import DropdownMenu from '../../../components/dropdown_menu';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import Link from 'react-router-dom/Link';
|
||||
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
|
@ -96,7 +96,7 @@ export default class ActionBar extends React.PureComponent {
|
|||
|
||||
<div className='account__action-bar'>
|
||||
<div className='account__action-bar-dropdown'>
|
||||
<DropdownMenu items={menu} icon='bars' size={24} direction='right' />
|
||||
<DropdownMenuContainer items={menu} icon='bars' size={24} direction='right' />
|
||||
</div>
|
||||
|
||||
<div className='account__action-bar-links'>
|
||||
|
|
|
@ -24,6 +24,10 @@ const iconStyle = {
|
|||
export default class PrivacyDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -34,7 +38,25 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
};
|
||||
|
||||
handleToggle = () => {
|
||||
this.setState({ open: !this.state.open });
|
||||
if (this.props.isUserTouching()) {
|
||||
if (this.state.open) {
|
||||
this.props.onModalClose();
|
||||
} else {
|
||||
this.props.onModalOpen({
|
||||
actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })),
|
||||
onClick: this.handleModalActionClick,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({ open: !this.state.open });
|
||||
}
|
||||
}
|
||||
|
||||
handleModalActionClick = (e) => {
|
||||
e.preventDefault();
|
||||
const { value } = this.options[e.currentTarget.getAttribute('data-index')];
|
||||
this.props.onModalClose();
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
|
@ -50,6 +72,17 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
|
||||
this.options = [
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
];
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('click', this.onGlobalClick);
|
||||
window.addEventListener('touchstart', this.onGlobalClick);
|
||||
|
@ -68,25 +101,18 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
const { value, intl } = this.props;
|
||||
const { open } = this.state;
|
||||
|
||||
const options = [
|
||||
{ icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) },
|
||||
{ icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) },
|
||||
{ icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) },
|
||||
];
|
||||
|
||||
const valueOption = options.find(item => item.value === value);
|
||||
const valueOption = this.options.find(item => item.value === value);
|
||||
|
||||
return (
|
||||
<div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
|
||||
<div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
|
||||
<div className='privacy-dropdown__dropdown'>
|
||||
{open && options.map(item =>
|
||||
{open && this.options.map(item =>
|
||||
<div role='button' tabIndex='0' key={item.value} data-index={item.value} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
|
||||
<div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
<strong>{item.shortText}</strong>
|
||||
{item.longText}
|
||||
<strong>{item.text}</strong>
|
||||
{item.meta}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||
import { changeComposeVisibility } from '../../../actions/compose';
|
||||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
value: state.getIn(['compose', 'privacy']),
|
||||
});
|
||||
|
||||
|
@ -12,6 +15,10 @@ const mapDispatchToProps = dispatch => ({
|
|||
dispatch(changeComposeVisibility(value));
|
||||
},
|
||||
|
||||
isUserTouching,
|
||||
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
||||
onModalClose: () => dispatch(closeModal()),
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import DropdownMenu from '../../../components/dropdown_menu';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -84,7 +84,7 @@ export default class ActionBar extends React.PureComponent {
|
|||
<div className='detailed-status__button'><IconButton animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
||||
|
||||
<div className='detailed-status__action-bar-dropdown'>
|
||||
<DropdownMenu size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' />
|
||||
<DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
|
||||
export default class ReportModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
renderAction = (action, i) => {
|
||||
if (action === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown__sep' />;
|
||||
}
|
||||
|
||||
const { icon = null, text, meta = null, active = false, href = '#' } = action;
|
||||
|
||||
return (
|
||||
<li key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={active && 'active'}>
|
||||
{icon && <IconButton title={text} icon={icon} />}
|
||||
<div>
|
||||
<div>{text}</div>
|
||||
<div>{meta}</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const status = this.props.status && (
|
||||
<div className='status light'>
|
||||
<div className='boost-modal__status-header'>
|
||||
<div className='boost-modal__status-time'>
|
||||
<a href={this.props.status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
|
||||
<RelativeTimestamp timestamp={this.props.status.get('created_at')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href={this.props.status.getIn(['account', 'url'])} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
<Avatar src={this.props.status.getIn(['account', 'avatar'])} staticSrc={this.props.status.getIn(['account', 'avatar_static'])} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={this.props.status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={this.props.status} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal actions-modal'>
|
||||
{status}
|
||||
|
||||
<ul>
|
||||
{this.props.actions.map(this.renderAction)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import spring from 'react-motion/lib/spring';
|
|||
import BundleContainer from '../containers/bundle_container';
|
||||
import BundleModalError from './bundle_modal_error';
|
||||
import ModalLoading from './modal_loading';
|
||||
import ActionsModal from '../components/actions_modal';
|
||||
import {
|
||||
MediaModal,
|
||||
OnboardingModal,
|
||||
|
@ -21,6 +22,7 @@ const MODAL_COMPONENTS = {
|
|||
'BOOST': BoostModal,
|
||||
'CONFIRM': ConfirmationModal,
|
||||
'REPORT': ReportModal,
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
};
|
||||
|
||||
export default class ModalRoot extends React.PureComponent {
|
||||
|
|
|
@ -5,6 +5,15 @@ export function isMobile(width) {
|
|||
};
|
||||
|
||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
let userTouching = false;
|
||||
|
||||
window.addEventListener('touchstart', () => {
|
||||
userTouching = true;
|
||||
}, { once: true });
|
||||
|
||||
export function isUserTouching() {
|
||||
return userTouching;
|
||||
}
|
||||
|
||||
export function isIOS() {
|
||||
return iOS;
|
||||
|
|
|
@ -214,16 +214,18 @@
|
|||
}
|
||||
|
||||
.dropdown--active::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 4.5px 7.8px;
|
||||
border-color: transparent transparent $ui-secondary-color;
|
||||
bottom: 8px;
|
||||
right: 104px;
|
||||
@media screen and (min-width: 1025px) {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 4.5px 7.8px;
|
||||
border-color: transparent transparent $ui-secondary-color;
|
||||
bottom: 8px;
|
||||
right: 104px;
|
||||
}
|
||||
}
|
||||
|
||||
.invisible {
|
||||
|
@ -3402,7 +3404,8 @@ button.icon-button.active i.fa-retweet {
|
|||
|
||||
.boost-modal,
|
||||
.confirmation-modal,
|
||||
.report-modal {
|
||||
.report-modal,
|
||||
.actions-modal {
|
||||
background: lighten($ui-secondary-color, 8%);
|
||||
color: $ui-base-color;
|
||||
border-radius: 8px;
|
||||
|
@ -3493,6 +3496,43 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.actions-modal {
|
||||
.status {
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
|
||||
ul {
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
|
||||
li:not(:empty) {
|
||||
a {
|
||||
color: $ui-base-color;
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
|
||||
&.active {
|
||||
&,
|
||||
button {
|
||||
background: $ui-highlight-color;
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
button:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-modal__action-bar {
|
||||
.confirmation-modal__cancel-button {
|
||||
background-color: transparent;
|
||||
|
|
|
@ -5,16 +5,24 @@ import React from 'react';
|
|||
import DropdownMenu from '../../../app/javascript/mastodon/components/dropdown_menu';
|
||||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||
|
||||
const isTrue = () => true;
|
||||
|
||||
describe('<DropdownMenu />', () => {
|
||||
const icon = 'my-icon';
|
||||
const size = 123;
|
||||
const action = sinon.spy();
|
||||
let items;
|
||||
let wrapper;
|
||||
let action;
|
||||
|
||||
const items = [
|
||||
{ text: 'first item', action: action, href: '/some/url' },
|
||||
{ text: 'second item', action: 'noop' },
|
||||
];
|
||||
const wrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} />);
|
||||
beforeEach(() => {
|
||||
action = sinon.spy();
|
||||
|
||||
items = [
|
||||
{ text: 'first item', action: action, href: '/some/url' },
|
||||
{ text: 'second item', action: 'noop' },
|
||||
];
|
||||
wrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} />);
|
||||
});
|
||||
|
||||
it('contains one <Dropdown />', () => {
|
||||
expect(wrapper).to.have.exactly(1).descendants(Dropdown);
|
||||
|
@ -28,6 +36,16 @@ describe('<DropdownMenu />', () => {
|
|||
expect(wrapper.find(Dropdown)).to.have.exactly(1).descendants(DropdownContent);
|
||||
});
|
||||
|
||||
it('does not contain a <DropdownContent /> if isUserTouching', () => {
|
||||
const touchingWrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} isUserTouching={isTrue} />);
|
||||
expect(touchingWrapper.find(Dropdown)).to.have.exactly(0).descendants(DropdownContent);
|
||||
});
|
||||
|
||||
it('does not contain a <DropdownContent /> if isUserTouching', () => {
|
||||
const touchingWrapper = shallow(<DropdownMenu icon={icon} items={items} size={size} isUserTouching={isTrue} />);
|
||||
expect(touchingWrapper.find(Dropdown)).to.have.exactly(0).descendants(DropdownContent);
|
||||
});
|
||||
|
||||
it('uses props.size for <DropdownTrigger /> style values', () => {
|
||||
['font-size', 'width', 'line-height'].map((property) => {
|
||||
expect(wrapper.find(DropdownTrigger)).to.have.style(property, `${size}px`);
|
||||
|
@ -53,6 +71,23 @@ describe('<DropdownMenu />', () => {
|
|||
expect(wrapper.state('expanded')).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('calls onModalOpen when clicking the trigger if isUserTouching', () => {
|
||||
const onModalOpen = sinon.spy();
|
||||
const touchingWrapper = mount(<DropdownMenu icon={icon} items={items} status={3.14} size={size} onModalOpen={onModalOpen} isUserTouching={isTrue} />);
|
||||
touchingWrapper.find(DropdownTrigger).first().simulate('click');
|
||||
expect(onModalOpen.calledOnce).to.be.equal(true);
|
||||
expect(onModalOpen.args[0][0]).to.be.deep.equal({ status: 3.14, actions: items, onClick: touchingWrapper.node.handleClick });
|
||||
});
|
||||
|
||||
it('calls onModalClose when clicking an action if isUserTouching and isModalOpen', () => {
|
||||
const onModalOpen = sinon.spy();
|
||||
const onModalClose = sinon.spy();
|
||||
const touchingWrapper = mount(<DropdownMenu icon={icon} items={items} status={3.14} size={size} isModalOpen onModalOpen={onModalOpen} onModalClose={onModalClose} isUserTouching={isTrue} />);
|
||||
touchingWrapper.find(DropdownTrigger).first().simulate('click');
|
||||
touchingWrapper.node.handleClick({ currentTarget: { getAttribute: () => '0' }, preventDefault: () => null });
|
||||
expect(onModalClose.calledOnce).to.be.equal(true);
|
||||
});
|
||||
|
||||
// Error: ReactWrapper::state() can only be called on the root
|
||||
/*it('sets expanded to false when clicking outside', () => {
|
||||
const wrapper = mount((
|
||||
|
|
Loading…
Reference in New Issue