Improve privacy dropdown, remove react-simple-dropdown dependency (#5140)
* Improve privacy dropdown, remove react-simple-dropdown dependency * Animate privacy warning * Fix react-router-scroll
This commit is contained in:
		
							parent
							
								
									0b3f1ec62a
								
							
						
					
					
						commit
						cdad7977fc
					
				
					 4 changed files with 138 additions and 72 deletions
				
			
		| 
						 | 
					@ -2,7 +2,10 @@ import React from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { injectIntl, defineMessages } from 'react-intl';
 | 
					import { injectIntl, defineMessages } from 'react-intl';
 | 
				
			||||||
import IconButton from '../../../components/icon_button';
 | 
					import IconButton from '../../../components/icon_button';
 | 
				
			||||||
 | 
					import { Overlay } from 'react-overlays';
 | 
				
			||||||
 | 
					import { Motion, spring } from 'react-motion';
 | 
				
			||||||
import detectPassiveEvents from 'detect-passive-events';
 | 
					import detectPassiveEvents from 'detect-passive-events';
 | 
				
			||||||
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
  public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
 | 
					  public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
 | 
				
			||||||
| 
						 | 
					@ -16,10 +19,77 @@ const messages = defineMessages({
 | 
				
			||||||
  change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
 | 
					  change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const iconStyle = {
 | 
					const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
 | 
				
			||||||
  height: null,
 | 
					
 | 
				
			||||||
  lineHeight: '27px',
 | 
					class PrivacyDropdownMenu extends React.PureComponent {
 | 
				
			||||||
};
 | 
					
 | 
				
			||||||
 | 
					  static propTypes = {
 | 
				
			||||||
 | 
					    style: PropTypes.object,
 | 
				
			||||||
 | 
					    items: PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					    value: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    onClose: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    onChange: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleDocumentClick = e => {
 | 
				
			||||||
 | 
					    if (this.node && !this.node.contains(e.target)) {
 | 
				
			||||||
 | 
					      this.props.onClose();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleClick = e => {
 | 
				
			||||||
 | 
					    if (e.key === 'Escape') {
 | 
				
			||||||
 | 
					      this.props.onClose();
 | 
				
			||||||
 | 
					    } else if (!e.key || e.key === 'Enter') {
 | 
				
			||||||
 | 
					      const value = e.currentTarget.getAttribute('data-index');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.props.onClose();
 | 
				
			||||||
 | 
					      this.props.onChange(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount () {
 | 
				
			||||||
 | 
					    document.addEventListener('click', this.handleDocumentClick, false);
 | 
				
			||||||
 | 
					    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentWillUnmount () {
 | 
				
			||||||
 | 
					    document.removeEventListener('click', this.handleDocumentClick, false);
 | 
				
			||||||
 | 
					    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setRef = c => {
 | 
				
			||||||
 | 
					    this.node = c;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render () {
 | 
				
			||||||
 | 
					    const { style, items, value } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}>
 | 
				
			||||||
 | 
					            {items.map(item =>
 | 
				
			||||||
 | 
					              <div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })}>
 | 
				
			||||||
 | 
					                <div className='privacy-dropdown__option__icon'>
 | 
				
			||||||
 | 
					                  <i className={`fa fa-fw fa-${item.icon}`} />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className='privacy-dropdown__option__content'>
 | 
				
			||||||
 | 
					                  <strong>{item.text}</strong>
 | 
				
			||||||
 | 
					                  {item.meta}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Motion>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@injectIntl
 | 
					@injectIntl
 | 
				
			||||||
export default class PrivacyDropdown extends React.PureComponent {
 | 
					export default class PrivacyDropdown extends React.PureComponent {
 | 
				
			||||||
| 
						 | 
					@ -55,26 +125,30 @@ export default class PrivacyDropdown extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleModalActionClick = (e) => {
 | 
					  handleModalActionClick = (e) => {
 | 
				
			||||||
    e.preventDefault();
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { value } = this.options[e.currentTarget.getAttribute('data-index')];
 | 
					    const { value } = this.options[e.currentTarget.getAttribute('data-index')];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.props.onModalClose();
 | 
					    this.props.onModalClose();
 | 
				
			||||||
    this.props.onChange(value);
 | 
					    this.props.onChange(value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleClick = (e) => {
 | 
					  handleKeyDown = e => {
 | 
				
			||||||
    if (e.key === 'Escape') {
 | 
					    switch(e.key) {
 | 
				
			||||||
      this.setState({ open: false });
 | 
					    case 'Enter':
 | 
				
			||||||
    } else if (!e.key || e.key === 'Enter') {
 | 
					      this.handleToggle();
 | 
				
			||||||
      const value = e.currentTarget.getAttribute('data-index');
 | 
					      break;
 | 
				
			||||||
      e.preventDefault();
 | 
					    case 'Escape':
 | 
				
			||||||
      this.setState({ open: false });
 | 
					      this.handleClose();
 | 
				
			||||||
      this.props.onChange(value);
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onGlobalClick = (e) => {
 | 
					  handleClose = () => {
 | 
				
			||||||
    if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
 | 
					    this.setState({ open: false });
 | 
				
			||||||
      this.setState({ open: false });
 | 
					  }
 | 
				
			||||||
    }
 | 
					
 | 
				
			||||||
 | 
					  handleChange = value => {
 | 
				
			||||||
 | 
					    this.props.onChange(value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillMount () {
 | 
					  componentWillMount () {
 | 
				
			||||||
| 
						 | 
					@ -88,20 +162,6 @@ export default class PrivacyDropdown extends React.PureComponent {
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidMount () {
 | 
					 | 
				
			||||||
    window.addEventListener('click', this.onGlobalClick);
 | 
					 | 
				
			||||||
    window.addEventListener('touchstart', this.onGlobalClick, detectPassiveEvents.hasSupport ? { passive: true } : false);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentWillUnmount () {
 | 
					 | 
				
			||||||
    window.removeEventListener('click', this.onGlobalClick);
 | 
					 | 
				
			||||||
    window.removeEventListener('touchstart', this.onGlobalClick, detectPassiveEvents.hasSupport ? { passive: true } : false);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setRef = (c) => {
 | 
					 | 
				
			||||||
    this.node = c;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { value, intl } = this.props;
 | 
					    const { value, intl } = this.props;
 | 
				
			||||||
    const { open } = this.state;
 | 
					    const { open } = this.state;
 | 
				
			||||||
| 
						 | 
					@ -109,19 +169,29 @@ export default class PrivacyDropdown extends React.PureComponent {
 | 
				
			||||||
    const valueOption = this.options.find(item => item.value === value);
 | 
					    const valueOption = this.options.find(item => item.value === value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
 | 
					      <div className={classNames('privacy-dropdown', { active: open })} onKeyDown={this.handleKeyDown}>
 | 
				
			||||||
        <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} expanded={open} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
 | 
					        <div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}>
 | 
				
			||||||
        <div className='privacy-dropdown__dropdown'>
 | 
					          <IconButton
 | 
				
			||||||
          {open && this.options.map(item =>
 | 
					            className='privacy-dropdown__value-icon'
 | 
				
			||||||
            <div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
 | 
					            icon={valueOption.icon}
 | 
				
			||||||
              <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
 | 
					            title={intl.formatMessage(messages.change_privacy)}
 | 
				
			||||||
              <div className='privacy-dropdown__option__content'>
 | 
					            size={18}
 | 
				
			||||||
                <strong>{item.text}</strong>
 | 
					            expanded={open}
 | 
				
			||||||
                {item.meta}
 | 
					            active={open}
 | 
				
			||||||
              </div>
 | 
					            inverted
 | 
				
			||||||
            </div>
 | 
					            onClick={this.handleToggle}
 | 
				
			||||||
          )}
 | 
					            style={{ height: null, lineHeight: '27px' }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Overlay show={open} placement='bottom' target={this}>
 | 
				
			||||||
 | 
					          <PrivacyDropdownMenu
 | 
				
			||||||
 | 
					            items={this.options}
 | 
				
			||||||
 | 
					            value={value}
 | 
				
			||||||
 | 
					            onClose={this.handleClose}
 | 
				
			||||||
 | 
					            onChange={this.handleChange}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </Overlay>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import { Motion, spring } from 'react-motion';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class Warning extends React.PureComponent {
 | 
					export default class Warning extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,9 +12,13 @@ export default class Warning extends React.PureComponent {
 | 
				
			||||||
    const { message } = this.props;
 | 
					    const { message } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='compose-form__warning'>
 | 
					      <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 }) }}>
 | 
				
			||||||
        {message}
 | 
					        {({ opacity, scaleX, scaleY }) => (
 | 
				
			||||||
      </div>
 | 
					          <div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
 | 
				
			||||||
 | 
					            {message}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Motion>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1275,7 +1275,7 @@
 | 
				
			||||||
  background: $ui-secondary-color;
 | 
					  background: $ui-secondary-color;
 | 
				
			||||||
  padding: 4px 0;
 | 
					  padding: 4px 0;
 | 
				
			||||||
  border-radius: 4px;
 | 
					  border-radius: 4px;
 | 
				
			||||||
  box-shadow: 0 0 15px rgba($base-shadow-color, 0.4);
 | 
					  box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ul {
 | 
					  ul {
 | 
				
			||||||
    list-style: none;
 | 
					    list-style: none;
 | 
				
			||||||
| 
						 | 
					@ -2805,19 +2805,12 @@ button.icon-button.active i.fa-retweet {
 | 
				
			||||||
  filter: none;
 | 
					  filter: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.privacy-dropdown {
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.privacy-dropdown__dropdown {
 | 
					.privacy-dropdown__dropdown {
 | 
				
			||||||
  display: none;
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					  position: absolute;
 | 
				
			||||||
  left: 0;
 | 
					 | 
				
			||||||
  top: 27px;
 | 
					 | 
				
			||||||
  width: 230px;
 | 
					 | 
				
			||||||
  background: $simple-background-color;
 | 
					  background: $simple-background-color;
 | 
				
			||||||
  border-radius: 0 4px 4px;
 | 
					  box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
 | 
				
			||||||
  z-index: 2;
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					  margin-left: 40px;
 | 
				
			||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2869,6 +2862,18 @@ button.icon-button.active i.fa-retweet {
 | 
				
			||||||
    background: $simple-background-color;
 | 
					    background: $simple-background-color;
 | 
				
			||||||
    border-radius: 4px 4px 0 0;
 | 
					    border-radius: 4px 4px 0 0;
 | 
				
			||||||
    box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
 | 
					    box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .icon-button {
 | 
				
			||||||
 | 
					      transition: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.active {
 | 
				
			||||||
 | 
					      background: $ui-highlight-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .icon-button {
 | 
				
			||||||
 | 
					        color: $primary-text-color;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .privacy-dropdown__dropdown {
 | 
					  .privacy-dropdown__dropdown {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,22 +128,8 @@ body.rtl {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .privacy-dropdown__dropdown {
 | 
					  .privacy-dropdown__dropdown {
 | 
				
			||||||
    left: auto;
 | 
					    margin-left: 0;
 | 
				
			||||||
    right: 0;
 | 
					    margin-right: 40px;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .dropdown--active .dropdown__content {
 | 
					 | 
				
			||||||
    text-align: right;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .dropdown--active .dropdown__content::before {
 | 
					 | 
				
			||||||
    left: auto;
 | 
					 | 
				
			||||||
    right: 8px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .dropdown--active .dropdown__content > ul {
 | 
					 | 
				
			||||||
    left: auto;
 | 
					 | 
				
			||||||
    right: -10px;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .privacy-dropdown__option__icon {
 | 
					  .privacy-dropdown__option__icon {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue