]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/features/compose/components/privacy_dropdown.js
Use passive listener in privacy_dropdown.js (#5037)
[mastodon.git] / app / javascript / mastodon / features / compose / components / privacy_dropdown.js
1 import React from 'react';
2 import PropTypes from 'prop-types';
3 import { injectIntl, defineMessages } from 'react-intl';
4 import IconButton from '../../../components/icon_button';
5 import detectPassiveEvents from 'detect-passive-events';
6
7 const messages = defineMessages({
8 public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
9 public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
10 unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
11 unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
12 private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
13 private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
14 direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
15 direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' },
16 change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
17 });
18
19 const iconStyle = {
20 height: null,
21 lineHeight: '27px',
22 };
23
24 @injectIntl
25 export default class PrivacyDropdown extends React.PureComponent {
26
27 static propTypes = {
28 isUserTouching: PropTypes.func,
29 isModalOpen: PropTypes.bool.isRequired,
30 onModalOpen: PropTypes.func,
31 onModalClose: PropTypes.func,
32 value: PropTypes.string.isRequired,
33 onChange: PropTypes.func.isRequired,
34 intl: PropTypes.object.isRequired,
35 };
36
37 state = {
38 open: false,
39 };
40
41 handleToggle = () => {
42 if (this.props.isUserTouching()) {
43 if (this.state.open) {
44 this.props.onModalClose();
45 } else {
46 this.props.onModalOpen({
47 actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })),
48 onClick: this.handleModalActionClick,
49 });
50 }
51 } else {
52 this.setState({ open: !this.state.open });
53 }
54 }
55
56 handleModalActionClick = (e) => {
57 e.preventDefault();
58 const { value } = this.options[e.currentTarget.getAttribute('data-index')];
59 this.props.onModalClose();
60 this.props.onChange(value);
61 }
62
63 handleClick = (e) => {
64 if (e.key === 'Escape') {
65 this.setState({ open: false });
66 } else if (!e.key || e.key === 'Enter') {
67 const value = e.currentTarget.getAttribute('data-index');
68 e.preventDefault();
69 this.setState({ open: false });
70 this.props.onChange(value);
71 }
72 }
73
74 onGlobalClick = (e) => {
75 if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
76 this.setState({ open: false });
77 }
78 }
79
80 componentWillMount () {
81 const { intl: { formatMessage } } = this.props;
82
83 this.options = [
84 { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
85 { icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
86 { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
87 { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
88 ];
89 }
90
91 componentDidMount () {
92 window.addEventListener('click', this.onGlobalClick);
93 window.addEventListener('touchstart', this.onGlobalClick, detectPassiveEvents.hasSupport ? { passive: true } : false);
94 }
95
96 componentWillUnmount () {
97 window.removeEventListener('click', this.onGlobalClick);
98 window.removeEventListener('touchstart', this.onGlobalClick, detectPassiveEvents.hasSupport ? { passive: true } : false);
99 }
100
101 setRef = (c) => {
102 this.node = c;
103 }
104
105 render () {
106 const { value, intl } = this.props;
107 const { open } = this.state;
108
109 const valueOption = this.options.find(item => item.value === value);
110
111 return (
112 <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
113 <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>
114 <div className='privacy-dropdown__dropdown'>
115 {open && this.options.map(item =>
116 <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' : ''}`}>
117 <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
118 <div className='privacy-dropdown__option__content'>
119 <strong>{item.text}</strong>
120 {item.meta}
121 </div>
122 </div>
123 )}
124 </div>
125 </div>
126 );
127 }
128
129 }
This page took 0.106368 seconds and 5 git commands to generate.