* Make dropdowns render into portal, expand animation * Improve actions modal stylemaster
@@ -1,53 +1,55 @@ | |||
import React from 'react'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; | |||
import PropTypes from 'prop-types'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import IconButton from './icon_button'; | |||
import { Overlay } from 'react-overlays'; | |||
import { Motion, spring } from 'react-motion'; | |||
export default class DropdownMenu extends React.PureComponent { | |||
class DropdownMenu extends React.PureComponent { | |||
static contextTypes = { | |||
router: PropTypes.object, | |||
}; | |||
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, | |||
onClose: PropTypes.func.isRequired, | |||
style: PropTypes.object, | |||
placement: PropTypes.string, | |||
arrowOffsetLeft: PropTypes.string, | |||
arrowOffsetTop: PropTypes.string, | |||
}; | |||
static defaultProps = { | |||
ariaLabel: 'Menu', | |||
isModalOpen: false, | |||
isUserTouching: () => false, | |||
style: {}, | |||
placement: 'bottom', | |||
}; | |||
state = { | |||
direction: 'left', | |||
expanded: false, | |||
}; | |||
handleDocumentClick = e => { | |||
if (this.node && !this.node.contains(e.target)) { | |||
this.props.onClose(); | |||
} | |||
} | |||
componentDidMount () { | |||
document.addEventListener('click', this.handleDocumentClick, false); | |||
document.addEventListener('touchend', this.handleDocumentClick, false); | |||
} | |||
componentWillUnmount () { | |||
document.removeEventListener('click', this.handleDocumentClick, false); | |||
document.removeEventListener('touchend', this.handleDocumentClick, false); | |||
} | |||
setRef = (c) => { | |||
this.dropdown = c; | |||
setRef = c => { | |||
this.node = c; | |||
} | |||
handleClick = (e) => { | |||
handleClick = e => { | |||
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 | |||
this.props.onClose(); | |||
if (typeof action === 'function') { | |||
e.preventDefault(); | |||
@@ -56,90 +58,149 @@ export default class DropdownMenu extends React.PureComponent { | |||
e.preventDefault(); | |||
this.context.router.history.push(to); | |||
} | |||
} | |||
renderItem (option, i) { | |||
if (option === null) { | |||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />; | |||
} | |||
const { text, href = '#' } = option; | |||
return ( | |||
<li className='dropdown-menu__item' key={`${text}-${i}`}> | |||
<a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' autoFocus={i === 0} onClick={this.handleClick} data-index={i}> | |||
{text} | |||
</a> | |||
</li> | |||
); | |||
} | |||
render () { | |||
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props; | |||
this.dropdown.hide(); | |||
return ( | |||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> | |||
{({ opacity, scaleX, scaleY }) => ( | |||
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}> | |||
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} /> | |||
<ul> | |||
{items.map((option, i) => this.renderItem(option, i))} | |||
</ul> | |||
</div> | |||
)} | |||
</Motion> | |||
); | |||
} | |||
handleShow = () => { | |||
if (this.props.isUserTouching()) { | |||
} | |||
export default class Dropdown extends React.PureComponent { | |||
static contextTypes = { | |||
router: PropTypes.object, | |||
}; | |||
static propTypes = { | |||
icon: PropTypes.string.isRequired, | |||
items: PropTypes.array.isRequired, | |||
size: PropTypes.number.isRequired, | |||
ariaLabel: PropTypes.string, | |||
disabled: PropTypes.bool, | |||
status: ImmutablePropTypes.map, | |||
isUserTouching: PropTypes.func, | |||
isModalOpen: PropTypes.bool.isRequired, | |||
onModalOpen: PropTypes.func, | |||
onModalClose: PropTypes.func, | |||
}; | |||
static defaultProps = { | |||
ariaLabel: 'Menu', | |||
}; | |||
state = { | |||
expanded: false, | |||
}; | |||
handleClick = () => { | |||
if (!this.state.expanded && this.props.isUserTouching() && this.props.onModalOpen) { | |||
const { status, items } = this.props; | |||
this.props.onModalOpen({ | |||
status: this.props.status, | |||
actions: this.props.items, | |||
onClick: this.handleClick, | |||
status, | |||
actions: items, | |||
onClick: this.handleItemClick, | |||
}); | |||
} else { | |||
this.setState({ expanded: true }); | |||
return; | |||
} | |||
this.setState({ expanded: !this.state.expanded }); | |||
} | |||
handleHide = () => this.setState({ expanded: false }) | |||
handleToggle = (e) => { | |||
if (e.key === 'Enter') { | |||
if (this.props.isUserTouching()) { | |||
this.handleShow(); | |||
} else { | |||
this.setState({ expanded: !this.state.expanded }); | |||
} | |||
} else if (e.key === 'Escape') { | |||
this.setState({ expanded: false }); | |||
handleClose = () => { | |||
if (this.props.onModalClose) { | |||
this.props.onModalClose(); | |||
} | |||
this.setState({ expanded: false }); | |||
} | |||
renderItem = (item, i) => { | |||
if (item === null) { | |||
return <li key={`sep-${i}`} className='dropdown__sep' />; | |||
handleKeyDown = e => { | |||
switch(e.key) { | |||
case 'Enter': | |||
this.handleClick(); | |||
break; | |||
case 'Escape': | |||
this.handleClose(); | |||
break; | |||
} | |||
} | |||
const { text, href = '#' } = item; | |||
handleItemClick = e => { | |||
const i = Number(e.currentTarget.getAttribute('data-index')); | |||
const { action, to } = this.props.items[i]; | |||
return ( | |||
<li className='dropdown__content-list-item' key={`${text}-${i}`}> | |||
<a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' autoFocus={i === 0} onClick={this.handleClick} data-index={i} className='dropdown__content-list-link'> | |||
{text} | |||
</a> | |||
</li> | |||
); | |||
} | |||
this.handleClose(); | |||
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`; | |||
if (disabled) { | |||
return ( | |||
<div className='icon-button disabled' style={iconStyle} aria-label={ariaLabel}> | |||
<i className={iconClassname} aria-hidden /> | |||
</div> | |||
); | |||
if (typeof action === 'function') { | |||
e.preventDefault(); | |||
action(); | |||
} else if (to) { | |||
e.preventDefault(); | |||
this.context.router.history.push(to); | |||
} | |||
} | |||
const dropdownItems = expanded && ( | |||
<ul role='group' className='dropdown__content-list' onClick={this.handleHide}> | |||
{items.map(this.renderItem)} | |||
</ul> | |||
); | |||
setTargetRef = c => { | |||
this.target = c; | |||
} | |||
// 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 />; | |||
findTarget = () => { | |||
return this.target; | |||
} | |||
return ( | |||
<Dropdown ref={this.setRef} active={isUserTouching ? false : expanded} onShow={this.handleShow} onHide={this.handleHide}> | |||
<DropdownTrigger className='icon-button' style={iconStyle} role='button' aria-expanded={expanded} onKeyDown={this.handleToggle} tabIndex='0' aria-label={ariaLabel}> | |||
<i className={iconClassname} aria-hidden /> | |||
</DropdownTrigger> | |||
render () { | |||
const { icon, items, size, ariaLabel, disabled } = this.props; | |||
const { expanded } = this.state; | |||
{dropdownContent} | |||
</Dropdown> | |||
return ( | |||
<div onKeyDown={this.handleKeyDown}> | |||
<IconButton | |||
icon={icon} | |||
title={ariaLabel} | |||
active={expanded} | |||
disabled={disabled} | |||
size={size} | |||
ref={this.setTargetRef} | |||
onClick={this.handleClick} | |||
/> | |||
<Overlay show={expanded} placement='bottom' target={this.findTarget}> | |||
<DropdownMenu items={items} onClose={this.handleClose} /> | |||
</Overlay> | |||
</div> | |||
); | |||
} | |||
@@ -1,32 +1,35 @@ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
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'; | |||
import classNames from 'classnames'; | |||
export default class ActionsModal extends ImmutablePureComponent { | |||
static propTypes = { | |||
status: ImmutablePropTypes.map, | |||
actions: PropTypes.array, | |||
onClick: PropTypes.func, | |||
}; | |||
renderAction = (action, i) => { | |||
if (action === null) { | |||
return <li key={`sep-${i}`} className='dropdown__sep' />; | |||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />; | |||
} | |||
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'}> | |||
<a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={classNames({ active })}> | |||
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' />} | |||
<div> | |||
<div>{text}</div> | |||
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div> | |||
<div>{meta}</div> | |||
</div> | |||
</a> | |||
@@ -52,7 +52,7 @@ export default class UI extends React.PureComponent { | |||
static contextTypes = { | |||
router: PropTypes.object.isRequired, | |||
} | |||
}; | |||
static propTypes = { | |||
dispatch: PropTypes.func.isRequired, | |||
@@ -183,14 +183,18 @@ export default class UI extends React.PureComponent { | |||
document.removeEventListener('dragend', this.handleDragEnd); | |||
} | |||
setRef = (c) => { | |||
setRef = c => { | |||
this.node = c; | |||
} | |||
setColumnsAreaRef = (c) => { | |||
setColumnsAreaRef = c => { | |||
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance(); | |||
} | |||
setOverlayRef = c => { | |||
this.overlay = c; | |||
} | |||
render () { | |||
const { width, draggingOver } = this.state; | |||
const { children } = this.props; | |||
@@ -213,6 +213,10 @@ | |||
} | |||
} | |||
.dropdown-menu { | |||
position: absolute; | |||
} | |||
.dropdown--active .icon-button { | |||
color: $ui-highlight-color; | |||
} | |||
@@ -694,8 +698,8 @@ | |||
.status__action-bar-dropdown { | |||
float: left; | |||
height: 18px; | |||
width: 18px; | |||
height: 23.15px; | |||
width: 23.15px; | |||
} | |||
.detailed-status__action-bar-dropdown { | |||
@@ -704,26 +708,6 @@ | |||
align-items: center; | |||
justify-content: center; | |||
position: relative; | |||
.dropdown { | |||
display: block; | |||
width: 18px; | |||
height: 18px; | |||
} | |||
.dropdown--active { | |||
.dropdown__content.dropdown__left { | |||
left: 20px; | |||
right: initial; | |||
} | |||
&::after { | |||
bottom: initial; | |||
margin-left: 7px; | |||
margin-top: -7px; | |||
right: initial; | |||
} | |||
} | |||
} | |||
.detailed-status { | |||
@@ -1254,10 +1238,80 @@ | |||
position: absolute; | |||
} | |||
.dropdown__sep { | |||
.dropdown-menu__separator { | |||
border-bottom: 1px solid darken($ui-secondary-color, 8%); | |||
margin: 5px 7px 6px; | |||
padding-top: 1px; | |||
height: 0; | |||
} | |||
.dropdown-menu { | |||
background: $ui-secondary-color; | |||
padding: 4px 0; | |||
border-radius: 4px; | |||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.4); | |||
ul { | |||
list-style: none; | |||
} | |||
} | |||
.dropdown-menu__arrow { | |||
position: absolute; | |||
width: 0; | |||
height: 0; | |||
border: 0 solid transparent; | |||
&.left { | |||
right: -5px; | |||
margin-top: -5px; | |||
border-width: 5px 0 5px 5px; | |||
border-left-color: $ui-secondary-color; | |||
} | |||
&.top { | |||
bottom: -5px; | |||
margin-left: -13px; | |||
border-width: 5px 5px 0; | |||
border-top-color: $ui-secondary-color; | |||
} | |||
&.bottom { | |||
top: -5px; | |||
margin-left: -13px; | |||
border-width: 0 5px 5px; | |||
border-bottom-color: $ui-secondary-color; | |||
} | |||
&.right { | |||
left: -5px; | |||
margin-top: -5px; | |||
border-width: 5px 5px 5px 0; | |||
border-right-color: $ui-secondary-color; | |||
} | |||
} | |||
.dropdown-menu__item { | |||
a { | |||
font-size: 13px; | |||
line-height: 18px; | |||
display: block; | |||
padding: 4px 14px; | |||
box-sizing: border-box; | |||
text-decoration: none; | |||
background: $ui-secondary-color; | |||
color: $ui-base-color; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
&:focus, | |||
&:hover, | |||
&:active { | |||
background: $ui-highlight-color; | |||
color: $ui-secondary-color; | |||
outline: 0; | |||
} | |||
} | |||
} | |||
.dropdown--active .dropdown__content { | |||
@@ -3472,6 +3526,10 @@ button.icon-button.active i.fa-retweet { | |||
padding-top: 10px; | |||
padding-bottom: 10px; | |||
} | |||
.dropdown-menu__separator { | |||
border-bottom-color: $ui-secondary-color; | |||
} | |||
} | |||
.boost-modal__container { | |||
@@ -3549,6 +3607,10 @@ button.icon-button.active i.fa-retweet { | |||
max-height: 80vh; | |||
max-width: 80vw; | |||
.actions-modal__item-label { | |||
font-weight: 500; | |||
} | |||
ul { | |||
overflow-y: auto; | |||
flex-shrink: 0; | |||
@@ -3561,11 +3623,20 @@ button.icon-button.active i.fa-retweet { | |||
a { | |||
color: $ui-base-color; | |||
display: flex; | |||
padding: 10px; | |||
padding: 12px 16px; | |||
font-size: 15px; | |||
align-items: center; | |||
text-decoration: none; | |||
&.active { | |||
&, | |||
button { | |||
transition: none; | |||
} | |||
&.active, | |||
&:hover, | |||
&:active, | |||
&:focus { | |||
&, | |||
button { | |||
background: $ui-highlight-color; | |||
@@ -88,6 +88,7 @@ | |||
"react-intl": "^2.4.0", | |||
"react-motion": "^0.5.0", | |||
"react-notification": "^6.7.1", | |||
"react-overlays": "^0.8.1", | |||
"react-redux": "^5.0.4", | |||
"react-redux-loading-bar": "^2.9.2", | |||
"react-router-dom": "^4.1.1", | |||
@@ -1,132 +0,0 @@ | |||
import { expect } from 'chai'; | |||
import { shallow, mount } from 'enzyme'; | |||
import sinon from 'sinon'; | |||
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; | |||
let items; | |||
let wrapper; | |||
let action; | |||
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); | |||
}); | |||
it('contains one <DropdownTrigger />', () => { | |||
expect(wrapper.find(Dropdown)).to.have.exactly(1).descendants(DropdownTrigger); | |||
}); | |||
it('contains one <DropdownContent />', () => { | |||
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`); | |||
}); | |||
}); | |||
it('uses props.icon as icon class name', () => { | |||
expect(wrapper.find(DropdownTrigger).find('i')).to.have.className(`fa-${icon}`); | |||
}); | |||
it('is not expanded by default', () => { | |||
expect(wrapper.state('expanded')).to.be.equal(false); | |||
}); | |||
it('does not render the list elements if not expanded', () => { | |||
const lis = wrapper.find(DropdownContent).find('li'); | |||
expect(lis.length).to.be.equal(0); | |||
}); | |||
it('sets expanded to true when clicking the trigger', () => { | |||
const wrapper = mount(<DropdownMenu icon={icon} items={items} size={size} />); | |||
wrapper.find(DropdownTrigger).first().simulate('click'); | |||
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(( | |||
<div> | |||
<DropdownMenu icon={icon} items={items} size={size} /> | |||
<span /> | |||
</div> | |||
)); | |||
wrapper.find(DropdownTrigger).first().simulate('click'); | |||
expect(wrapper.find(DropdownMenu).first().state('expanded')).to.be.equal(true); | |||
wrapper.find('span').first().simulate('click'); | |||
expect(wrapper.find(DropdownMenu).first().state('expanded')).to.be.equal(false); | |||
})*/ | |||
it('renders list elements for each props.items if expanded', () => { | |||
const wrapper = mount(<DropdownMenu icon={icon} items={items} size={size} />); | |||
wrapper.find(DropdownTrigger).first().simulate('click'); | |||
const lis = wrapper.find(DropdownContent).find('li'); | |||
expect(lis.length).to.be.equal(items.length); | |||
}); | |||
it('uses the href passed in via props.items', () => { | |||
wrapper | |||
.find(DropdownContent).find('li a') | |||
.forEach((a, i) => expect(a).to.have.attr('href', items[i].href)); | |||
}); | |||
it('uses the text passed in via props.items', () => { | |||
wrapper | |||
.find(DropdownContent).find('li a') | |||
.forEach((a, i) => expect(a).to.have.text(items[i].text)); | |||
}); | |||
it('uses the action passed in via props.items as click handler', () => { | |||
const wrapper = mount(<DropdownMenu icon={icon} items={items} size={size} />); | |||
wrapper.find(DropdownTrigger).first().simulate('click'); | |||
wrapper.find(DropdownContent).find('li a').first().simulate('click'); | |||
expect(action.calledOnce).to.equal(true); | |||
}); | |||
}); |
@@ -1234,6 +1234,10 @@ chai@^4.1.0: | |||
pathval "^1.0.0" | |||
type-detect "^4.0.0" | |||
chain-function@^1.0.0: | |||
version "1.0.0" | |||
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" | |||
chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: | |||
version "1.1.3" | |||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" | |||
@@ -1972,7 +1976,7 @@ doctrine@^2.0.0: | |||
esutils "^2.0.2" | |||
isarray "^1.0.0" | |||
"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.0.0, dom-helpers@^3.2.1: | |||
"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.0.0, dom-helpers@^3.2.0, dom-helpers@^3.2.1: | |||
version "3.2.1" | |||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" | |||
@@ -5131,6 +5135,12 @@ promise@^7.1.1: | |||
dependencies: | |||
asap "~2.0.3" | |||
prop-types-extra@^1.0.1: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.0.1.tgz#a57bd4810e82d27a3ff4317ecc1b4ad005f79a82" | |||
dependencies: | |||
warning "^3.0.0" | |||
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8: | |||
version "15.5.10" | |||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" | |||
@@ -5329,6 +5339,17 @@ react-notification@^6.7.1: | |||
dependencies: | |||
prop-types "^15.5.10" | |||
react-overlays@^0.8.1: | |||
version "0.8.1" | |||
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.1.tgz#26e480003c2fd6f581a4a66c0c86cb3dff17e626" | |||
dependencies: | |||
classnames "^2.2.5" | |||
dom-helpers "^3.2.1" | |||
prop-types "^15.5.10" | |||
prop-types-extra "^1.0.1" | |||
react-transition-group "^2.0.0-beta.0" | |||
warning "^3.0.0" | |||
react-redux-loading-bar@^2.9.2: | |||
version "2.9.2" | |||
resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.9.2.tgz#f0e604ee35af5ecb25addb10bf24ca3d478c95a8" | |||
@@ -5430,6 +5451,17 @@ react-toggle@^4.0.1: | |||
dependencies: | |||
classnames "^2.2.5" | |||
react-transition-group@^2.0.0-beta.0: | |||
version "2.2.0" | |||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.0.tgz#793bf8cb15bfe91b3101b24bce1c1d2891659575" | |||
dependencies: | |||
chain-function "^1.0.0" | |||
classnames "^2.2.5" | |||
dom-helpers "^3.2.0" | |||
loose-envify "^1.3.1" | |||
prop-types "^15.5.8" | |||
warning "^3.0.0" | |||
react-virtualized@^9.7.4: | |||
version "9.9.0" | |||
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.9.0.tgz#799a6f23819eeb82860d59b82fad33d1d420325e" | |||