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
					
				| @ -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 }) }}> | ||||||
|  |         {({ opacity, scaleX, scaleY }) => ( | ||||||
|  |           <div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}> | ||||||
|             {message} |             {message} | ||||||
|           </div> |           </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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user